Compare commits
No commits in common. "master" and "0.14.0" have entirely different histories.
1
.gitattributes
vendored
@ -1 +0,0 @@
|
||||
/packages/node_modules/** linguist-generated=false
|
61
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,61 +0,0 @@
|
||||
name: 🐞 Report a bug
|
||||
description: File a bug/issue on the core of Node-RED
|
||||
labels: [needs-triage]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
|
||||
|
||||
If your issue is:
|
||||
- a general 'how-to' type question,
|
||||
- a feature request or suggestion for a change,
|
||||
- or problems with 3rd party (`node-red-contrib-`) nodes
|
||||
|
||||
please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
|
||||
|
||||
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
|
||||
|
||||
That way the whole Node-RED user community can help, rather than rely on the core development team.
|
||||
|
||||
To help us understand the issue, please fill-in as much of the following information as you can:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: A clear & concise description of what you're experiencing.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear & concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Example flow
|
||||
description: If you have a minimal example flow that demonstrates the issue, share it here.
|
||||
value: |
|
||||
```
|
||||
paste your flow here
|
||||
```
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Please tell us about your environment. Include any relevant information on how you are running Node-RED.
|
||||
value: |
|
||||
- Node-RED version:
|
||||
- Node.js version:
|
||||
- npm version:
|
||||
- Platform/OS:
|
||||
- Browser:
|
||||
validations:
|
||||
required: false
|
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,14 +0,0 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: ❓ Questions
|
||||
url: https://discourse.nodered.org
|
||||
about: Ask your question on the Node-RED forum
|
||||
- name: ⭐️ Feature Request
|
||||
url: https://discourse.nodered.org/c/development/feature-requests
|
||||
about: Discuss your request with the community
|
||||
- name: 🗂 Documentation
|
||||
url: https://nodered.org/docs
|
||||
about: Go straight to the documentation
|
||||
- name: 💬 Slack
|
||||
url: https://nodered.org/slack
|
||||
about: Chat about the project on our slack team
|
34
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,34 +0,0 @@
|
||||
<!--
|
||||
## Before you hit that Submit button....
|
||||
|
||||
Please read our [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
|
||||
before submitting a pull-request.
|
||||
|
||||
## Types of changes
|
||||
|
||||
What types of changes does your code introduce?
|
||||
Put an `x` in the boxes that apply
|
||||
-->
|
||||
|
||||
- [ ] Bugfix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
|
||||
<!--
|
||||
If you want to raise a pull-request with a new feature, or a refactoring
|
||||
of existing code, it **may well get rejected** if it hasn't been discussed on
|
||||
the [forum](https://discourse.nodered.org) or
|
||||
[slack team](https://nodered.org/slack) first.
|
||||
|
||||
-->
|
||||
|
||||
## Proposed changes
|
||||
|
||||
<!-- Describe the nature of this change. What problem does it address? -->
|
||||
|
||||
## Checklist
|
||||
<!-- Put an `x` in the boxes that apply -->
|
||||
|
||||
- [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
|
||||
- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team.
|
||||
- [ ] I have run `npm run test` to verify the unit tests pass
|
||||
- [ ] I have added suitable unit tests to cover the new/changed functionality
|
15
.github/dependabot.yml
vendored
@ -1,15 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
29
.github/scripts/update-node-red-docker.js
vendored
@ -1,29 +0,0 @@
|
||||
const fs = require("fs");
|
||||
|
||||
const newVersion = require("../../package.json").version;
|
||||
|
||||
if (process.env.GITHUB_REF !== "refs/tags/"+newVersion) {
|
||||
console.log(`GITHUB_REF doesn't match the package.json version: ${process.env.GITHUB_REF} !== ${newVersion}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!/^\d+\.\d+\.\d+$/.test(newVersion)) {
|
||||
console.log(`Not updating for a non-stable release - ${newVersion}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const currentVersion = require("../../../node-red-docker/package.json").version;
|
||||
|
||||
console.log(`Update from ${currentVersion} to ${newVersion}`)
|
||||
|
||||
updateFile(__dirname+"/../../../node-red-docker/package.json", currentVersion, newVersion);
|
||||
updateFile(__dirname+"/../../../node-red-docker/docker-custom/package.json", currentVersion, newVersion);
|
||||
updateFile(__dirname+"/../../../node-red-docker/README.md", currentVersion, newVersion);
|
||||
|
||||
console.log(`::set-env name=newVersion::${newVersion}`);
|
||||
|
||||
function updateFile(path,from,to) {
|
||||
let contents = fs.readFileSync(path,"utf8");
|
||||
contents = contents.replace(new RegExp(from.replace(/\./g,"\\."),"g"), to);
|
||||
fs.writeFileSync(path, contents);
|
||||
}
|
18
.github/scripts/update-node-red-website.js
vendored
@ -1,18 +0,0 @@
|
||||
const fs = require("fs");
|
||||
|
||||
const newVersion = require("../../package.json").version;
|
||||
|
||||
if (process.env.GITHUB_REF !== "refs/tags/"+newVersion) {
|
||||
console.log(`GITHUB_REF doesn't match the package.json version: ${process.env.GITHUB_REF} !== ${newVersion}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!/^\d+\.\d+\.\d+$/.test(newVersion)) {
|
||||
console.log(`Not updating for a non-stable release - ${newVersion}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const path = __dirname+"/../../../node-red.github.io/index.html";
|
||||
let contents = fs.readFileSync(path, "utf8");
|
||||
contents = contents.replace(/<span class="node-red-latest-version">v\d+\.\d+\.\d+<\/span>/, `<span class="node-red-latest-version">v${newVersion}<\/span>` );
|
||||
fs.writeFileSync(path, contents);
|
62
.github/workflows/release.yml
vendored
@ -1,62 +0,0 @@
|
||||
name: Publish Release
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
name: 'Update node-red-docker image'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out node-red repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: 'node-red'
|
||||
- name: Check out node-red-docker repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'node-red/node-red-docker'
|
||||
path: 'node-red-docker'
|
||||
- name: Check out node-red.github.io repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'node-red/node-red.github.io'
|
||||
path: 'node-red.github.io'
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16'
|
||||
- run: node ./node-red/.github/scripts/update-node-red-docker.js
|
||||
- name: Create Docker Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.NR_REPO_TOKEN }}
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
path: 'node-red-docker'
|
||||
commit-message: 'Bump to ${{ env.newVersion }}'
|
||||
title: '🚀 Update to Node-RED ${{ env.newVersion }} release'
|
||||
body: |
|
||||
Updates the Node-RED Docker repo for the ${{ env.newVersion }} release.
|
||||
|
||||
Once this is merged, you will need to create a new release with the tag `v${{ env.newVersion }}`.
|
||||
|
||||
This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary
|
||||
- run: node ./node-red/.github/scripts/update-node-red-website.js
|
||||
- name: Create Website Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.NR_REPO_TOKEN }}
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
path: 'node-red.github.io'
|
||||
commit-message: 'Bump to ${{ env.newVersion }}'
|
||||
title: '🚀 Update to Node-RED ${{ env.newVersion }} release'
|
||||
body: |
|
||||
Updates the Node-RED Website repo for the ${{ env.newVersion }} release.
|
||||
|
||||
This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary
|
30
.github/workflows/tests.yml
vendored
@ -1,30 +0,0 @@
|
||||
name: Run tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev ]
|
||||
pull_request:
|
||||
branches: [ master, dev ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18, 20, 22]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
- name: Run tests
|
||||
run: |
|
||||
npm run test
|
12
.gitignore
vendored
@ -7,9 +7,7 @@
|
||||
.sessions.json
|
||||
.settings
|
||||
.tern-project
|
||||
.i18n-editor-metadata
|
||||
*.backup
|
||||
*.bak
|
||||
*_cred*
|
||||
coverage
|
||||
credentials.json
|
||||
@ -19,13 +17,3 @@ node_modules
|
||||
public
|
||||
locales/zz-ZZ
|
||||
nodes/core/locales/zz-ZZ
|
||||
!packages/node_modules
|
||||
packages/node_modules/@node-red/editor-client/public
|
||||
!test/**/node_modules
|
||||
docs
|
||||
!packages/node_modules/**/docs
|
||||
.vscode
|
||||
.nyc_output
|
||||
sync.ffs_db
|
||||
package-lock.json
|
||||
.editorconfig
|
||||
|
@ -2,9 +2,6 @@
|
||||
"asi": true, // allow missing semicolons
|
||||
"curly": true, // require braces
|
||||
"eqnull": true, // ignore ==null
|
||||
//"eqeqeq": true, // enforce ===
|
||||
"freeze": true, // don't allow override
|
||||
"indent": 4, // default indent of 4
|
||||
"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
|
||||
@ -12,8 +9,6 @@
|
||||
//"unused": true, // Check for unused functions and variables
|
||||
"loopfunc": true, // allow functions to be defined in loops
|
||||
//"expr": true, // allow ternery operator syntax...
|
||||
"shadow": true, // allow variable shadowing (re-use of names...)
|
||||
"sub": true, // don't warn that foo['bar'] should be written as foo.bar
|
||||
"proto": true, // allow setting of __proto__ in node < v0.12,
|
||||
"esversion": 11 // allow es11(ES2020)
|
||||
"proto": true // allow setting of __proto__ in node < v0.12
|
||||
}
|
||||
|
5
.nodemonignore
Normal file
@ -0,0 +1,5 @@
|
||||
/Gruntfile.js
|
||||
/.git/*
|
||||
/lib/*
|
||||
*.backup
|
||||
/public/*
|
7
.npmignore
Normal file
@ -0,0 +1,7 @@
|
||||
.settings
|
||||
.jshintignore
|
||||
.jshintrc
|
||||
.project
|
||||
.tern-project
|
||||
.travis.yml
|
||||
.git
|
26
.travis.yml
Normal file
@ -0,0 +1,26 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
env:
|
||||
- CXX="g++-4.8"
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
- gcc-4.8
|
||||
matrix:
|
||||
allow_failures:
|
||||
- node_js: "5"
|
||||
- node_js: "6"
|
||||
node_js:
|
||||
- "6"
|
||||
- "5"
|
||||
- "4"
|
||||
- "0.12"
|
||||
- "0.10"
|
||||
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:
|
||||
- npm install -g istanbul
|
||||
- npm install coveralls
|
19
API.md
@ -1,19 +0,0 @@
|
||||
Node-RED consists of 6 node modules under the `@node-red` scope, which are pulled together
|
||||
by the top-level `node-red` module. The typical scenario is where you are embedding Node-RED into your
|
||||
own application, in which case you would use the `node-red` module rather than any of the
|
||||
internal modules directly.
|
||||
|
||||
```javascript
|
||||
let RED = require("node-red");
|
||||
```
|
||||
|
||||
|
||||
Module | Description
|
||||
-------|-------
|
||||
[node-red](node-red.html) | the main module that pulls together all of the internal modules and provides the executable version of Node-RED
|
||||
[@node-red/editor-api](@node-red_editor-api.html) | an Express application that serves the Node-RED editor and provides the Admin HTTP API
|
||||
[@node-red/runtime](@node-red_runtime.html) | the core runtime of Node-RED
|
||||
[@node-red/util](@node-red_util.html) | common utilities for the Node-RED runtime and editor modules
|
||||
[@node-red/registry](@node-red_registry.html) | the internal node registry
|
||||
@node-red/nodes | the default set of core nodes. This module only contains the Node-RED nodes - it does not expose any APIs.
|
||||
@node-red/editor-client | the client-side resources of the Node-RED editor application
|
506
CHANGELOG.md
@ -1,307 +1,345 @@
|
||||
#### 4.0.9: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Add details for the dynamic subscription to match the English docs (#5050) @aikitori
|
||||
- Fix tooltip snapping based on `typedInput` type (#5051) @GogoVega
|
||||
- Prevent symbol usage warning in monaco (#5049) @Steve-Mcl
|
||||
- Show subflow flow context under node section of sidebar (#5025) @knolleary
|
||||
- feat: Add custom label for default deploy button in settings.editorTheme (#5030) @matiseni51
|
||||
- Handle long auto-complete suggests (#5042) @knolleary
|
||||
- Handle undefined username when generating user icon (#5043) @knolleary
|
||||
- Handle dragging node into group and splicing link at same time (#5027) @knolleary
|
||||
- Remember context sidebar tree state when refreshing (#5021) @knolleary
|
||||
- Update sf instance env vars when removed from template (#5023) @knolleary
|
||||
- Do not select group when triggering quick-add within it (#5022) @knolleary
|
||||
- Fix library icon handling within library browser component (#5017) @knolleary
|
||||
|
||||
Runtime
|
||||
- Allow env var access to context (#5016) @knolleary
|
||||
- fix debug status reporting if null (#5018) @dceejay
|
||||
- Fix grunt dev via better ndoemon ignore rules (#5015) @knolleary
|
||||
- Fix typo in CHANGELOG (4.0.7-->4.0.8) (#5007) @natcl
|
||||
|
||||
Nodes
|
||||
- Switch: Avoid exceeding call stack when draining message group in Switch (#5014) @knolleary
|
||||
|
||||
#### 4.0.8: Maintenance Release
|
||||
#### 0.14.0: Milestone Release
|
||||
|
||||
Editor
|
||||
|
||||
- Fix config node sort order when importing (#5000) @knolleary
|
||||
|
||||
#### 4.0.7: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Fix def can be undefined if the type is missing (#4997) @GogoVega
|
||||
- Fix the user list of nested config node (#4995) @GogoVega
|
||||
- Support custom login message and button (#4993) @knolleary
|
||||
|
||||
#### 4.0.6: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Roll up various fixes on config node change history (#4975) @knolleary
|
||||
- Add quotes when installing local tgz to fix spacing in the file path (#4949) @AGhorab-upland
|
||||
- Validate json dropped into editor to avoid unhelpful error messages (#4964) @knolleary
|
||||
- Fix junction insert position via context menu (#4974) @knolleary
|
||||
- Apply zoom scale when calculating annotation positions (#4981) @knolleary
|
||||
- Handle the import of an incomplete Subflow (#4811) @GogoVega
|
||||
- Fix updating the Subflow name during a copy (#4809) @GogoVega
|
||||
- Rename variable to avoid confusion in view.js (#4963) @knolleary
|
||||
- Change groups.length to groups.size (#4959) @hungtcs
|
||||
- Remove disabled node types from QuickAddDialog list (#4946) @GogoVega
|
||||
- Fix `setModulePendingUpdated` with plugins (#4939) @GogoVega
|
||||
- Missing getSubscriptions in the docs while its implemented (#4934) @ersinpw
|
||||
- Apply `envVarExcludes` setting to `util.getSetting` into the function node (#4925) @GogoVega
|
||||
- Fix `envVar` editable list should be sortable (#4932) @GogoVega
|
||||
- Improve the node name auto-generated with the first available number (#4912) @GogoVega
|
||||
- Replace edit dialog with edit tray
|
||||
- Enable shift-drag detach of just the selected link
|
||||
- Allow workspace tabs to be re-ordered
|
||||
- Scope keyboard shortcuts to dom elements
|
||||
- Ensure parent nodes marked as changed due to child config node changes
|
||||
- Validate all edit dialog inputs when one changes
|
||||
- Add editableList widget and update Switch/Change nodes to use it
|
||||
- Add option to filter Debug sidebar by flow and highlight subflow-emitting nodes
|
||||
- Back off comms reconnect attempts after prolonged failures
|
||||
- Prompt for login if comms reconnect fails authentication
|
||||
- Change style of nodes in subflow template view
|
||||
- Add CHANGELOG.md and make it accessible from menu
|
||||
|
||||
Runtime
|
||||
|
||||
- Get the env config node from the parent subflow (#4960) @GogoVega
|
||||
- Update dependencies (#4987) @knolleary
|
||||
- Always log node warnings on start without requiring -v
|
||||
- Add support for loading scoped node modules. Closes #885
|
||||
- Add process.env.PORT to settings.js
|
||||
- Clear node context on deploy. Closes #870
|
||||
- Enable finer grained permissions in adminAuth
|
||||
|
||||
Nodes
|
||||
|
||||
- Performance : make reading single buffer / string file faster by not re-allocating and handling huge buffers (#4980) @Fadoli
|
||||
- Make delay node rate limit reset consistent - not send on reset. (#4940) @dceejay
|
||||
- Fix trigger node date handling for latest time type input (#4915) @dceejay
|
||||
- Fix delay node not dropping when nodeMessageBufferMaxLength is set (#4973)
|
||||
- Ensure node.sep is honoured when generating CSV (#4982) @knolleary
|
||||
- Enable config nodes to reference other config nodes
|
||||
- Add Split/Join nodes
|
||||
- Add Link nodes
|
||||
- Add support to HTTP In node for PATCH requests. Closes #904
|
||||
- Add cookie handling to HTTP In and HTTP Response nodes
|
||||
- Add repeat indicator to inject node label. Closes #887
|
||||
- Add javascript highlighter to template node
|
||||
- Add optional timeout to exec node
|
||||
- Add TLS node and update MQTT/HTTP nodes to use it
|
||||
- Let trigger node also send last payload to arrive
|
||||
- Add timestamp as a default typedInput and update Inject and change nodes to match,
|
||||
- Add QoS option to MQTT In node
|
||||
- Add status to exec spawn mode
|
||||
- Add Move capability to Change node
|
||||
- Update Serial node to support custom baud rates
|
||||
- Add support for array-syntax in typedInput msg properties
|
||||
- Add RED.util to Function node sandbox
|
||||
- Capture error stack on node.error. Closes #879
|
||||
|
||||
#### 4.0.5: Maintenance Release
|
||||
|
||||
Editor
|
||||
Fixes
|
||||
|
||||
- Refix link call node can call out of a subflow (#4908) @GogoVega
|
||||
- Add error handling to all node definition api calls
|
||||
- Handle null return from Function node in array of messages
|
||||
- Defer loading of token sessions until they are accessed. Fixes #895
|
||||
- set pi gpio pin status correctly if set on start
|
||||
- Prevent parent window scrolling when view is focused. Fixes #635
|
||||
- Handle missing tab nodes in a loaded flow config
|
||||
- Ensure typedInput dropdown doesn't fall off the page
|
||||
- Protect against node types with reserved names such as toString. Fixes #880
|
||||
- Do not rely on the HTML file to identify where nodes are registered from
|
||||
- Preserve node properties on import
|
||||
- Fix regression in delay node. topic based queue was emptying all the time instead of spreading out messages.
|
||||
- Throw an error if a Function node adds an input event listener
|
||||
- Fix hang on partial deploy with disconnected mqtt node
|
||||
- TypedInput: preload type icons to ensure width calc correct
|
||||
- Ensure tcp node creates a buffer of size 1 at least
|
||||
- Return editorTheme default if value is undefined
|
||||
- Fix RED.util.compareObjects for Function created objects and Buffers
|
||||
- Ensure default settings copied to command-line specified userDir
|
||||
|
||||
#### 4.0.4: Maintenance Release
|
||||
|
||||
Editor
|
||||
#### 0.13.4: Maintenance Release
|
||||
|
||||
- Fix `link call` node can call out of a subflow (#4892) @GogoVega
|
||||
- Fix wrong unlock state when event is triggered after deployment (#4889) @GogoVega
|
||||
- i18n(App) update with latest language file changes (#4903) @joebordes
|
||||
- fix typo: depreciated (#4895) @dxdc
|
||||
- Add timed release mode to delay node
|
||||
- Enable link splicing for when import_dragging nodes. Closes #811
|
||||
- Fix uncaught exception on deploy whilst node sending messages
|
||||
- Deprecate old mqtt client and connection pool modules
|
||||
- Change node: add bool/num types to change mode Closes #835
|
||||
- Validate fields that are `$(env-vars)` Closes #825
|
||||
- Handle missing config nodes when validating node properties
|
||||
- Pi node - don't try to send data if closing
|
||||
- Load node message catalog when added dynamically
|
||||
- Split palette labels on spaces and hyphens when laying out
|
||||
- Warn if editor routes are accessed but runtime not started Closes #816
|
||||
- Better handling of zero-length flow files Closes #819
|
||||
- Allow runtime calls to RED._ to specify other namespace
|
||||
- Better right alignment of numerics in delay and trigger nodes
|
||||
- Allow node modules to include example flows
|
||||
- Create node_modules in userDir
|
||||
- Ensure errors in node def functions don't break view rendering Fixes #815
|
||||
- Updated Inject node info with instructions for flow and global options
|
||||
|
||||
Runtime
|
||||
|
||||
- Update dev dependencies (#4893) @knolleary
|
||||
|
||||
Nodes
|
||||
#### 0.13.3: Maintenance Release
|
||||
|
||||
- MQTT: Allow msg.userProperties to have number values (#4900) @hardillb
|
||||
- Fix crash on repeated inject of invalid json payload
|
||||
- Add binary mode to tail node
|
||||
- Revert Cheerio to somewhat smaller version
|
||||
- Add os/platform info to default debug
|
||||
|
||||
#### 4.0.3: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Refresh page title after changing tab name (#4850) @kazuhitoyokoi
|
||||
- Add Japanese translations for v4.0.2 (again) (#4853) @kazuhitoyokoi
|
||||
- Stay in quick-add mode following context menu insert (#4883) @knolleary
|
||||
- Do not include Junction type in quick-add for virtual links (#4879) @knolleary
|
||||
- Multiplayer cursor tracking (#4845) @knolleary
|
||||
- Hide add-flow options when disabled via editorTheme (#4869) @knolleary
|
||||
- Fix env-var config select when multiple defined (#4872) @knolleary
|
||||
- Fix subflow outbound-link filter (#4857) @GogoVega
|
||||
- Add French translations for v4.0.2 (#4856) @GogoVega
|
||||
- Fix moving link wires (#4851) @knolleary
|
||||
- Adjust type search dialog position to prevent x-overflow (#4844) @Steve-Mcl
|
||||
- fix: modulesInUse might be undefined (#4838) @lorenz-maurer
|
||||
- Add Japanese translations for v4.0.2 (#4849) @kazuhitoyokoi
|
||||
- Fix menu to enable/disable selection when it's a group (#4828) @GogoVega
|
||||
#### 0.13.2: Maintenance Release
|
||||
|
||||
Runtime
|
||||
- Don't force reconnect mqtt client if message arrives (fixes the MQTT connect/disconnect endless cycle)
|
||||
- Add -p/--port option to override listening port
|
||||
- Invert config node filter toggle button colours so state is more obvious
|
||||
- Add timeout to httprequest node
|
||||
- Tidy up of all node info content - make style consistent
|
||||
- Make jquery spinner element css consistent with other inputs
|
||||
- tcp node add reply (to all) capability
|
||||
- Allow the template node to be treated as plain text
|
||||
- Validate MQTT In topics Fixes #792
|
||||
- httpNodeAuth should not block http options requests Fixes #793
|
||||
- Disable perMessageDeflate on WS servers - fixes 'zlib binding closed' error
|
||||
- Clear trigger status icon on re-deploy
|
||||
- Don't default inject payload to blank string
|
||||
- Trigger node, add configurable reset
|
||||
- Allow function properties in settings Fixes #790 - fixes use of httpNodeMiddleware
|
||||
- Fix order of config dialog calls to save/creds/validate
|
||||
- Add debounce to Pi GPIO node
|
||||
|
||||
- Update dependencies (#4874) @knolleary
|
||||
- GitHub: Add citation file to enable "Cite this repository" feature (#4861) @lobis
|
||||
- Remove use of util.log (#4875) @knolleary
|
||||
|
||||
Nodes
|
||||
|
||||
- Fix invalid property error in range node example (#4855)
|
||||
- Fix typo in flow example name (#4854) @kazuhitoyokoi
|
||||
- Move SNI, ALPN and Verify Server cert out of check (#4882) @hardillb
|
||||
- Set status of mqtt nodes to "disconnected" when deregistered from broker (#4878) @Steve-Mcl
|
||||
- MQTT: Ensure will payload is a string (#4873) @knolleary
|
||||
- Let batch node terminate "early" if msg.parts set to end of sequence (#4829) @dceejay
|
||||
- Fix unintentional Capitalisation in Split node name (#4835) @dceejay
|
||||
#### 0.13.1: Maintenance Release
|
||||
|
||||
#### 4.0.2: Maintenance Release
|
||||
- Revert wrapping of http request object
|
||||
|
||||
Editor
|
||||
|
||||
- Use a more subtle border on the header (#4818) @bonanitech
|
||||
- Improve the editor's French translations (#4824) @GogoVega
|
||||
- Clean up orphaned editors (#4821) @Steve-Mcl
|
||||
- Fix node validation if the property is not required (#4812) @GogoVega
|
||||
- Ensure mermaid.min.js is cached properly between loads of the editor (#4817) @knolleary
|
||||
|
||||
Runtime
|
||||
#### 0.13.0: Milestone Release
|
||||
|
||||
- Allow auth cookie name to be customised (#4815) @knolleary
|
||||
- Guard against undefined sessions in multiplayer (#4816) @knolleary
|
||||
- Add 'previous value' option to Switch node
|
||||
- Allow existing nodes to splice into links on drag
|
||||
- CORS not properly configured on multiple http routes Fixes #783
|
||||
- Restore shift-drag to snap/unsnap to grid
|
||||
- Moving nodes with keyboard should flag workspace dirty
|
||||
- Notifications flagged as fixed should not be click-closable
|
||||
- Rework config sidebar and deploy warning
|
||||
- Wrap http request object to match http response object
|
||||
- Add 'view' menu and reorganise a few things
|
||||
- Allow shift-click to detach existing wires
|
||||
- Splice nodes dragged from palette into links
|
||||
- try to trim imported/dragged flows to [ ]
|
||||
- Move version number as title of NR logo
|
||||
- Moving nodes mark workspace as dirty
|
||||
- Ok/Cancel edit dialogs with Ctrl-Enter/Escape
|
||||
- Handle OSX Meta key when selecting nodes
|
||||
- Add grid-alignment options
|
||||
- Add oneditresize function definition
|
||||
- Rename propertySelect to typedInput and add boolean opt
|
||||
- Add propertySelect to switch node
|
||||
- Add propertySelect support to Change node
|
||||
- Add context/flow/global support to Function node
|
||||
- Add node context/flow/global
|
||||
- Add propertySelect jquery widget
|
||||
- Add add/update/delete flow apis
|
||||
- Allow core nodes dir to be provided to runtime via settings
|
||||
- Tidy up API passed to node modules
|
||||
- Move locale files under api/runtime components
|
||||
- Add flow reload admin api
|
||||
|
||||
#### 4.0.1: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Ensure subflow instance credential property values are extracted (#4802) @knolleary
|
||||
- Use `_ADD_` value for both `add new...` and `none` options (#4800) @GogoVega
|
||||
- Fix the config node select value assignment (#4788) @GogoVega
|
||||
- Add tooltip for number of subflow instance on info tab (#4786) @kazuhitoyokoi
|
||||
- Add Japanese translations for v4.0.0 (#4785) @kazuhitoyokoi
|
||||
#### 0.12.5: Maintenance Release
|
||||
|
||||
Runtime
|
||||
- Add attribute capability to HTML parser node
|
||||
- Add Pi Keyboard code node
|
||||
- Fix for MQTT client connection cycling on partial deploy
|
||||
- Fix for tcp node properly closing connections
|
||||
- Update sentiment node dependencies
|
||||
- Fix for file node handling of UTF8 extended characters
|
||||
|
||||
- Ensure group nodes are properly exported in /flow api (#4803) @knolleary
|
||||
|
||||
Nodes
|
||||
|
||||
- Joins: make using msg.parts optional in join node (#4796) @dceejay
|
||||
- HTTP Request: UI proxy should setup agents for both http_proxy and https_proxy (#4794) @Steve-Mcl
|
||||
- HTTP Request: Remove default user agent (#4791) @Steve-Mcl
|
||||
#### 0.12.4: Maintenance Release
|
||||
|
||||
#### 4.0.0: Milestone Release
|
||||
- Add readOnly setting to prevent file writes in localfilesystem storage
|
||||
- Support bcrypt for httpNodeAuth
|
||||
- Pi no longer needs root workaround to access gpio
|
||||
- Fix: Input File node will not retain the file name
|
||||
|
||||
This marks the next major release of Node-RED. The following changes represent
|
||||
those added since the last beta. Check the beta release details below for the complete
|
||||
list.
|
||||
|
||||
Breaking Changes
|
||||
|
||||
- Node-RED now requires Node 18.x or later. At the time of release, we recommend
|
||||
using Node 20.
|
||||
#### 0.12.3: Maintenance Release
|
||||
|
||||
Editor
|
||||
- Fixes for TCP Get node reconnect handling
|
||||
- Clear delay node status on re-deploy
|
||||
- Update Font-Awesome to v4.5
|
||||
- Fix trigger to block properly until reset
|
||||
- Update example auth properties in settings.js
|
||||
- Ensure httpNodeAuth doesn't get applied to admin routes
|
||||
- TCP Get node not passing on existing msg properties
|
||||
|
||||
- Add `httpStaticCors` (#4761) @knolleary
|
||||
- Update dependencies (#4763) @knolleary
|
||||
- Sync master to dev (#4756) @knolleary
|
||||
- Add tooltip and message validation to `typedInput` (#4747) @GogoVega
|
||||
- Replace bcrypt with @node-rs/bcrypt (#4744) @knolleary
|
||||
- Export Nodes dialog refinement (#4746) @Steve-Mcl
|
||||
|
||||
#### 4.0.0-beta.4: Beta Release
|
||||
|
||||
Editor
|
||||
#### 0.12.2: Maintenance Release
|
||||
|
||||
- Fix the Sidebar Config is not refreshed after a deploy (#4734) @GogoVega
|
||||
- Fix checkboxes are not updated when calling `typedInput("value", "")` (#4729) @GogoVega
|
||||
- Fix panning with middle mouse button on windows 10/11 (#4716) @corentin-sodebo-voile
|
||||
- Add Japanese translation for sidebar tooltip (#4727) @kazuhitoyokoi
|
||||
- Translate the number of items selected in the options list (#4730) @GogoVega
|
||||
- Fix a checkbox should return a Boolean value and not the string `on` (#4715) @GogoVega
|
||||
- Deleting a grouped node should update the group (#4714) @GogoVega
|
||||
- Change the Config Node cursor to `pointer` (#4711) @GogoVega
|
||||
- Add missing tooltips to Sidebar (#4713) @GogoVega
|
||||
- Allow nodes to return additional history entries in onEditSave (#4710) @knolleary
|
||||
- Update to Monaco 0.49.0 (#4725) @Steve-Mcl
|
||||
- Add Japanese translations for 4.0.0-beta.3 (#4726) @kazuhitoyokoi
|
||||
- Show lock on deploy if user is read-only (#4706) @knolleary
|
||||
- Enable touch-menu for links so they can be deleted
|
||||
- Allow nodes to be installed by path name
|
||||
- Fix basic authentication on httpNode/Admin/Static
|
||||
- Handle errors thrown in Function node setTimeout/Interval
|
||||
- Fix mqtt node lifecycle with partial deployments
|
||||
- Update tcp node status on reconnect after timeout
|
||||
- Debug node not handling null messages
|
||||
- Kill processes run with exec node when flows redeployed
|
||||
- Inject time spinner incrementing value incorrectly
|
||||
|
||||
Runtime
|
||||
|
||||
- Ensure all CSS variables are in the output file (#3743) @bonanitech
|
||||
- Add httpAdminCookieOptions (#4718) @knolleary
|
||||
- chore: migrate deprecated `util.isArray` (#4724) @Rotzbua
|
||||
- Add --version cli args (#4707) @knolleary
|
||||
- feat(grunt): fail if files are missing (#4739) @Rotzbua
|
||||
- fix(node-red-pi): node-red not started by path (#4736) @Rotzbua
|
||||
- fix(editor): remove trailing slash (#4735) @Rotzbua
|
||||
- fix: remove deprecated mqtt.js (#4733) @Rotzbua
|
||||
|
||||
Nodes
|
||||
#### 0.12.1: Maintenance Release
|
||||
|
||||
- Perform Proxy logic more like cURL (#4616) @Steve-Mcl
|
||||
- Enable touch-menu for links so they can be deleted
|
||||
- Allow nodes to be installed by path name
|
||||
- Fix basic authentication on httpNode/Admin/Static
|
||||
|
||||
#### 4.0.0-beta.3: Beta Release
|
||||
|
||||
Editor
|
||||
|
||||
- Improve background-deploy notification handling (#4692) @knolleary
|
||||
- Hide workspace tab on middle mouse click (#4657) @Steve-Mcl
|
||||
- multiplayer: Add user presence indicators (#4666) @knolleary
|
||||
- Enable updating dependency node of package.json in project feature (#4676) @kazuhitoyokoi
|
||||
- Add French translations for 4.0.0-beta.2 (#4681) @GogoVega
|
||||
- Add Japanese translations for 4.0.0-beta.2 (#4674) @kazuhitoyokoi
|
||||
- Fix saving of conf-type properties in module packaged subflows (#4658) @knolleary
|
||||
- Add npm install timeout notification (#4662) @hardillb
|
||||
- Fix undo of subflow env property edits (#4667) @knolleary
|
||||
- Fix three error typos in monaco.js (#4660) @JoshuaCWebDeveloper
|
||||
- docs: Add closing paragraph tag (#4664) @ZJvandeWeg
|
||||
- Avoid login loops when autoLogin enabled but login fails (#4684) @knolleary
|
||||
#### 0.12.0: Milestone Release
|
||||
|
||||
Runtime
|
||||
- Change/Switch rules now resize with dialog width
|
||||
- Support for node 4.x
|
||||
- Move to Express 4.x
|
||||
- Copy default settings file to user dir on start up
|
||||
- Config nodes can be scoped to a particular subflow/tab
|
||||
- Comms link tolerates <5 second breaks in connection before notifying user
|
||||
- MQTT node overhaul - add will/tls/birth message support
|
||||
- Status node - to report status events from other nodes
|
||||
- Error node can be targeted to specific other nodes
|
||||
- JSON node can encode Array types
|
||||
- Switch node regular expression rule can now be set to be case-insensitive
|
||||
- HTTP In node can accept non-UTF8 payloads - will return a Buffer when appropriate
|
||||
- Exec node configuration consistent regardless of the spawn option
|
||||
- Function node can now display status icon/text
|
||||
- CSV node can now handle arrays
|
||||
- setInterval/clearInterval add to Function node
|
||||
- Function node automatically clears all timers (setInterval/setTimeout) when the node is stopped
|
||||
|
||||
- Allow blank strings to be used for env var property substitutions (#4672) @knolleary
|
||||
- Use rfdc for cloning pure JSON values (#4679) @knolleary
|
||||
- fix: remove outdated Node 11+ check (#4314) @Rotzbua
|
||||
- feat(ci): add new nodejs v22 (#4694) @Rotzbua
|
||||
- fix(node): increase required node >=18.5 (#4690) @Rotzbua
|
||||
- fix(dns): remove outdated node check (#4689) @Rotzbua
|
||||
- fix(polyfill): remove import module polyfill (#4688) @Rotzbua
|
||||
- Fix typo (#4686) @Rotzbua
|
||||
|
||||
Nodes
|
||||
|
||||
- Pass full error object in Function node and copy over cause property (#4685) @knolleary
|
||||
- Replacing vm.createScript in favour of vm.Script (#4534) @patlux
|
||||
#### 0.11.2: Maintenance Release
|
||||
|
||||
#### 4.0.0-beta.2: Beta Release
|
||||
- Allow XML parser options be set on the message
|
||||
- Add 'mobile' category to the palette (no core nodes included)
|
||||
- Allow a message catalog provide a partial translation
|
||||
- Fix HTTP Node nls message id
|
||||
- Remove delay spinner upper limit
|
||||
- Update debug node output to include length of payload
|
||||
|
||||
Editor
|
||||
|
||||
- Introduce multiplayer feature (#4629) @knolleary
|
||||
- Separate the "add new config-node" option into a new (+) button (#4627) @GogoVega
|
||||
- Retain Palette categories collapsed and filter to localStorage (#4634) @knolleary
|
||||
- Ensure palette filter reapplies and clear up unknown categories (#4637) @knolleary
|
||||
- Add support for plugin (only) modules to the palette manager (#4620) @knolleary
|
||||
- Update monaco to latest and node types to 18 LTS (#4615) @Steve-Mcl
|
||||
|
||||
Runtime
|
||||
|
||||
- Fix handling of subflow config-node select type in sf module (#4643) @knolleary
|
||||
- Comms API updates (#4628) @knolleary
|
||||
- Add French translations for 4.0.0-beta.1 (#4621) @GogoVega
|
||||
- Add Japanese translations for 4.0.0-beta.1 (#4612) @kazuhitoyokoi
|
||||
#### 0.11.1: Maintenance Release
|
||||
|
||||
Nodes
|
||||
- Fix change node handling of replacing with boolean (#4639) @knolleary
|
||||
- Fix exclusive config node check when type not registered (prevented HTTP In node from being editable unless the swagger node was also installed)
|
||||
|
||||
#### 4.0.0-beta.1: Beta Release
|
||||
|
||||
Editor
|
||||
|
||||
- Click on id in debug panel highlights node or flow (#4439) @ralphwetzel
|
||||
- Support config selection in a subflow env var (#4587) @Steve-Mcl
|
||||
- Add timestamp formatting options to TypedInput (#4468) @knolleary
|
||||
- Allow RED.view.select to select links (#4553) @lgrkvst
|
||||
- Add auto-complete to flow/global/env typedInput types (#4480) @knolleary
|
||||
- Improve the appearance of the Node-RED primary header (#4598) @joepavitt
|
||||
#### 0.11.0: Milestone Release
|
||||
|
||||
Runtime
|
||||
- Add Node 0.12 support
|
||||
- Internationalization support
|
||||
- Editor UI refresh
|
||||
- Add RBE node
|
||||
- File node optionally creates path to file
|
||||
- Function node can access `clearTimeout`
|
||||
- Fix: Unable to login with 'read' permission
|
||||
|
||||
- let settings.httpNodeAuth accept single middleware or array of middlewares (#4572) @kevinGodell
|
||||
- Upgrade to JSONata 2.x (#4590) @knolleary
|
||||
- Bump minimum version to node 18 (#4571) @knolleary
|
||||
- npm: Remove production flag on npm invocation (#4347) @ZJvandeWeg
|
||||
- Timer testing fix (#4367) @hlovdal
|
||||
- Bump to 4.0.0-dev (#4322) @knolleary
|
||||
|
||||
Nodes
|
||||
|
||||
- TCP node - when resetting, if no payload, stay disconnected @dceejay
|
||||
- HTML node: add option for collecting attributes and content (#4513) @gorenje
|
||||
- let split node specify property to split on, and join auto join correctly (#4386) @dceejay
|
||||
- Add RFC4180 compliant mode to CSV node (#4540) @Steve-Mcl
|
||||
- Fix change node to return boolean if asked (#4525) @dceejay
|
||||
- Let msg.reset reset Tcp request node connection when in stay connected mode (#4406) @dceejay
|
||||
- Let debug node status msg length be settable via settings (#4402) @dceejay
|
||||
- Feat: Add ability to set headers for WebSocket client (#4436) @marcus-j-davies
|
||||
#### 0.10.10: Maintenance Release
|
||||
|
||||
#### Older Releases
|
||||
- Fix permissions issue with packaged nrgpio script
|
||||
- Add better help message if deprecated node missing
|
||||
|
||||
Change logs for older releases are available on GitHub: https://github.com/node-red/node-red/releases
|
||||
|
||||
|
||||
#### 0.10.9: Maintenance Release
|
||||
|
||||
Fix packaging of bin scripts
|
||||
|
||||
|
||||
|
||||
#### 0.10.8: Maintenance Release
|
||||
|
||||
- Nodes moved out of core
|
||||
- still included as a dependency: twitter, serial, email, feedparser
|
||||
- no longer included: mongo, arduino, irc, redis
|
||||
- node icon defn can be a function
|
||||
- http_proxy support
|
||||
- httpNodeMiddleware setting
|
||||
- Trigger node ui refresh
|
||||
- editorTheme setting
|
||||
- Warn on deploy of unused config nodes
|
||||
- catch node prevents error loops
|
||||
|
||||
|
||||
|
||||
#### 0.10.6: Maintenance Release
|
||||
|
||||
Changes:
|
||||
- Performance improvements in editor
|
||||
- Palette appearance update
|
||||
- Warn on navigation with undeployed changes
|
||||
- Disable undeployed node action buttons
|
||||
- Disable subflow node action buttons
|
||||
- Add Catch node
|
||||
- Add logging functions to Function node
|
||||
- Add send function to Function node
|
||||
- Update Change node to support multiple rules
|
||||
|
||||
|
||||
|
||||
#### 0.10.4: Maintenance Release
|
||||
|
||||
Changes:
|
||||
|
||||
- http request node passes on request url as msg.url
|
||||
- handle config nodes appearing out of order in flow file - don't assume they are always at the start
|
||||
- move subflow palette category to the top, to make it more obvious
|
||||
- fix labelling of Raspberry Pi pins
|
||||
- allow email node to mark mail as read
|
||||
- fix saving library content
|
||||
- add node-red and node-red-pi start scripts
|
||||
- use $HOME/.node-red for user data unless specified otherwise (or existing data is found in install dir)
|
||||
|
||||
|
||||
|
||||
#### 0.10.3: Maintenance Release
|
||||
|
||||
Fixes:
|
||||
|
||||
- httpAdminAuth was too aggressively deprecated (ie removed); restoring with a console warning when used
|
||||
- adds reporting of node.js version on start-up
|
||||
- mongo node skip/limit options can be strings or numbers
|
||||
- CSV parser passes through provided message object
|
||||
|
||||
|
||||
|
||||
#### 0.10.2: Maintenance Release
|
||||
|
||||
Fixes:
|
||||
- subflow info sidebar more useful
|
||||
- adds missing font-awesome file
|
||||
- inject node day selection defaulted to invalid selection
|
||||
- loading a flow with no tabs failed to add nodes to default tab
|
||||
|
@ -1,7 +0,0 @@
|
||||
cff-version: 1.2.0
|
||||
message: "If you use this software, please cite it as below."
|
||||
title: "Node-RED"
|
||||
authors:
|
||||
- family-names: "OpenJS Foundation"
|
||||
- family-names: "Contributors"
|
||||
url: "https://nodered.org"
|
@ -9,16 +9,13 @@ We welcome contributions, but request you follow these guidelines.
|
||||
|
||||
This project adheres to the [Contributor Covenant 1.4](http://contributor-covenant.org/version/1/4/).
|
||||
By participating, you are expected to uphold this code. Please report unacceptable
|
||||
behavior to the project's core team at team@nodered.org.
|
||||
behavior to any of the [project's core team](https://github.com/orgs/node-red/teams/core).
|
||||
|
||||
## Raising issues
|
||||
|
||||
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.
|
||||
|
||||
If your issue is more of a question on how to do something with Node-RED, please
|
||||
consider using the [community forum](https://discourse.nodered.org/).
|
||||
|
||||
A good bug report is one that make it easy for us to understand what you were
|
||||
trying to do and what went wrong.
|
||||
|
||||
@ -29,34 +26,33 @@ relevant nodes, press Ctrl-E and copy the flow data from the Export dialog.
|
||||
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?
|
||||
- Version of node.js - what does `node -v` say?
|
||||
|
||||
## Feature requests
|
||||
|
||||
For feature requests, please raise them on the [forum](https://discourse.nodered.org).
|
||||
For feature requests, please raise them on the [mailing list](https://groups.google.com/forum/#!forum/node-red).
|
||||
|
||||
## Pull-Requests
|
||||
|
||||
If you want to raise a pull-request with a new feature, or a refactoring
|
||||
of existing code, please come and discuss it with us first. We prefer to
|
||||
do it that way to make sure your time and effort is well spent on something
|
||||
that fits with our goals.
|
||||
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.
|
||||
|
||||
If you've got a bug-fix or similar for us, then you are most welcome to
|
||||
get it raised - just make sure you link back to the issue it's fixing and
|
||||
try to include some tests!
|
||||
### Contributor License Agreement
|
||||
|
||||
All contributors need to sign the OpenJS Foundation's Contributor License Agreement.
|
||||
It is an online process and quick to do. If you raise a pull-request without
|
||||
having signed the CLA, you will be prompted to do so automatically.
|
||||
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
|
||||
change your rights to use your own Contributions for any other purpose.
|
||||
|
||||
### Code Branches
|
||||
You can download the CLAs here:
|
||||
|
||||
When raising a PR for a fix or a new feature, it is important to target the right branch.
|
||||
- [individual](http://nodered.org/cla/node-red-cla-individual.pdf)
|
||||
- [corporate](http://nodered.org/cla/node-red-cla-corporate.pdf)
|
||||
|
||||
- `master` - this is the main branch for the latest stable release of Node-RED. All bug fixes for that release should target this branch.
|
||||
- `v1.x` - this is the maintenance branch for the 1.x stream. If a fix *only* applies to 1.x, then it should target this branch. If it applies to the current stable release as well, target `master` first. We will then decide if it needs to be back ported to the 1.x stream.
|
||||
- `dev` - this is the branch for new feature development targeting the next milestone release.
|
||||
If you are an IBMer, please contact us directly as the contribution process is
|
||||
slightly different.
|
||||
|
||||
### Coding standards
|
||||
|
||||
|
628
Gruntfile.js
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
* 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,35 +15,17 @@
|
||||
**/
|
||||
|
||||
var path = require("path");
|
||||
var fs = require("fs-extra");
|
||||
var sass = require("sass");
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
var nodemonArgs = ["-V"];
|
||||
var nodemonArgs = ["-v"];
|
||||
var flowFile = grunt.option('flowFile');
|
||||
if (flowFile) {
|
||||
nodemonArgs.push(flowFile);
|
||||
process.env.NODE_RED_ENABLE_PROJECTS=false;
|
||||
}
|
||||
var userDir = grunt.option('userDir');
|
||||
if (userDir) {
|
||||
nodemonArgs.push("-u");
|
||||
nodemonArgs.push(userDir);
|
||||
}
|
||||
|
||||
var browserstack = grunt.option('browserstack');
|
||||
if (browserstack) {
|
||||
process.env.BROWSERSTACK = true;
|
||||
}
|
||||
var nonHeadless = grunt.option('non-headless');
|
||||
if (nonHeadless) {
|
||||
process.env.NODE_RED_NON_HEADLESS = true;
|
||||
}
|
||||
const pkg = grunt.file.readJSON('package.json');
|
||||
process.env.NODE_RED_PACKAGE_VERSION = pkg.version;
|
||||
grunt.initConfig({
|
||||
pkg: pkg,
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
paths: {
|
||||
dist: ".dist"
|
||||
},
|
||||
@ -55,29 +37,10 @@ module.exports = function(grunt) {
|
||||
ui: 'bdd',
|
||||
reporter: 'spec'
|
||||
},
|
||||
all: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js","test/nodes/**/*_spec.js"] },
|
||||
core: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js"]},
|
||||
all: { src: ['test/**/*_spec.js'] },
|
||||
core: { src: ["test/_spec.js","test/red/**/*_spec.js"]},
|
||||
nodes: { src: ["test/nodes/**/*_spec.js"]}
|
||||
},
|
||||
webdriver: {
|
||||
all: {
|
||||
configFile: 'test/editor/wdio.conf.js'
|
||||
}
|
||||
},
|
||||
nyc: {
|
||||
options: {
|
||||
cwd: '.',
|
||||
include: ['packages/node_modules/**'],
|
||||
excludeNodeModules: false,
|
||||
exclude: ['packages/node_modules/@node-red/editor-client/**'],
|
||||
reporter: ['lcov', 'html','text-summary'],
|
||||
reportDir: 'coverage',
|
||||
all: true
|
||||
},
|
||||
all: { cmd: false, args: ['grunt', 'simplemocha:all'] },
|
||||
core: { options: { exclude:['packages/node_modules/@node-red/editor-client/**', 'packages/node_modules/@node-red/nodes/**']},cmd: false, args: ['grunt', 'simplemocha:core'] },
|
||||
nodes: { cmd: false, args: ['grunt', 'simplemocha:nodes'] }
|
||||
},
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc:true
|
||||
@ -92,20 +55,22 @@ module.exports = function(grunt) {
|
||||
//"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',
|
||||
// 'packages/**/*.js'
|
||||
// ],
|
||||
// core: {
|
||||
// files: {
|
||||
// src: [
|
||||
// 'Gruntfile.js',
|
||||
// 'red.js',
|
||||
// 'packages/**/*.js',
|
||||
// ]
|
||||
// }
|
||||
// },
|
||||
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' ]
|
||||
@ -113,7 +78,7 @@ module.exports = function(grunt) {
|
||||
},
|
||||
editor: {
|
||||
files: {
|
||||
src: [ 'packages/node_modules/@node-red/editor-client/src/js/**/*.js' ]
|
||||
src: [ 'editor/js/**/*.js' ]
|
||||
}
|
||||
},
|
||||
tests: {
|
||||
@ -133,188 +98,108 @@ module.exports = function(grunt) {
|
||||
src: [
|
||||
// Ensure editor source files are concatenated in
|
||||
// the right order
|
||||
"packages/node_modules/@node-red/editor-client/src/js/polyfills.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/jquery-addons.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/red.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/events.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/hooks.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/i18n.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/settings.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/user.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/comms.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/runtime.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/multiplayer.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/plugins.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/nodes.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/font-awesome.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/history.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/validators.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/utils.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/checkboxSet.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/panels.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/toggleButton.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/actions.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/diagnostics.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/palette.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editor.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/*.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/*.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tray.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/library.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/search.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/group.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/touch/radialMenu.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tour/*.js"
|
||||
"editor/js/main.js",
|
||||
"editor/js/events.js",
|
||||
"editor/js/i18n.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/popover.js",
|
||||
"editor/js/ui/workspaces.js",
|
||||
"editor/js/ui/view.js",
|
||||
"editor/js/ui/sidebar.js",
|
||||
"editor/js/ui/palette.js",
|
||||
"editor/js/ui/tab-info.js",
|
||||
"editor/js/ui/tab-config.js",
|
||||
"editor/js/ui/editor.js",
|
||||
"editor/js/ui/tray.js",
|
||||
"editor/js/ui/clipboard.js",
|
||||
"editor/js/ui/library.js",
|
||||
"editor/js/ui/notifications.js",
|
||||
"editor/js/ui/subflow.js",
|
||||
"editor/js/ui/touch/radialMenu.js",
|
||||
"editor/js/ui/typedInput.js",
|
||||
"editor/js/ui/editableList.js"
|
||||
],
|
||||
nonull: true,
|
||||
dest: "packages/node_modules/@node-red/editor-client/public/red/red.js"
|
||||
dest: "public/red/red.js"
|
||||
},
|
||||
vendor: {
|
||||
files: [
|
||||
{
|
||||
src: [
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.5.1.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
|
||||
"node_modules/marked/marked.min.js",
|
||||
"node_modules/dompurify/dist/purify.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
|
||||
"node_modules/i18next/i18next.min.js",
|
||||
"node_modules/i18next-http-backend/i18nextHttpBackend.min.js",
|
||||
"node_modules/jquery-i18next/jquery-i18next.min.js",
|
||||
"node_modules/jsonata/jsonata-es5.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ace.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.js"
|
||||
],
|
||||
nonull: true,
|
||||
dest: "packages/node_modules/@node-red/editor-client/public/vendor/vendor.js"
|
||||
},
|
||||
// {
|
||||
// src: [
|
||||
// // TODO: resolve relative resource paths in
|
||||
// // bootstrap/FA/jquery
|
||||
// ],
|
||||
// dest: "packages/node_modules/@node-red/editor-client/public/vendor/vendor.css"
|
||||
// },
|
||||
{
|
||||
src: [
|
||||
"node_modules/jsonata/jsonata-es5.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js"
|
||||
],
|
||||
nonull: true,
|
||||
dest: "packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js",
|
||||
},
|
||||
{
|
||||
src: "node_modules/mermaid/dist/mermaid.min.js",
|
||||
nonull: true,
|
||||
dest: "packages/node_modules/@node-red/editor-client/public/vendor/mermaid/mermaid.min.js",
|
||||
},
|
||||
]
|
||||
files: {
|
||||
"public/vendor/vendor.js": [
|
||||
"editor/vendor/jquery/js/jquery-1.11.3.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/d3/d3.v3.min.js",
|
||||
"editor/vendor/i18next/i18next.min.js"
|
||||
],
|
||||
"public/vendor/vendor.css": [
|
||||
// TODO: resolve relative resource paths in
|
||||
// bootstrap/FA/jquery
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
uglify: {
|
||||
build: {
|
||||
files: {
|
||||
'packages/node_modules/@node-red/editor-client/public/red/red.min.js': 'packages/node_modules/@node-red/editor-client/public/red/red.js',
|
||||
'packages/node_modules/@node-red/editor-client/public/red/main.min.js': 'packages/node_modules/@node-red/editor-client/public/red/main.js',
|
||||
'packages/node_modules/@node-red/editor-client/public/vendor/ace/mode-jsonata.js': 'packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js',
|
||||
'packages/node_modules/@node-red/editor-client/public/vendor/ace/snippets/jsonata.js': 'packages/node_modules/@node-red/editor-client/src/vendor/jsonata/snippets-jsonata.js'
|
||||
'public/red/red.min.js': 'public/red/red.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
sass: {
|
||||
build: {
|
||||
options: {
|
||||
implementation: sass,
|
||||
outputStyle: 'compressed'
|
||||
},
|
||||
files: [{
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/style.min.css',
|
||||
src: 'packages/node_modules/@node-red/editor-client/src/sass/style.scss'
|
||||
dest: 'public/red/style.min.css',
|
||||
src: 'editor/sass/style.scss'
|
||||
},
|
||||
{
|
||||
dest: 'public/vendor/bootstrap/css/bootstrap.min.css',
|
||||
src: 'editor/vendor/bootstrap/css/bootstrap.css'
|
||||
}]
|
||||
}
|
||||
},
|
||||
jsonlint: {
|
||||
messages: {
|
||||
src: [
|
||||
'packages/node_modules/@node-red/nodes/locales/**/*.json',
|
||||
'packages/node_modules/@node-red/editor-client/locales/**/*.json',
|
||||
'packages/node_modules/@node-red/runtime/locales/**/*.json'
|
||||
]
|
||||
},
|
||||
keymaps: {
|
||||
src: [
|
||||
'packages/node_modules/@node-red/editor-client/src/js/keymap.json'
|
||||
'nodes/core/locales/en-US/messages.json',
|
||||
'red/api/locales/en-US/editor.json',
|
||||
'red/runtime/locales/en-US/runtime.json'
|
||||
]
|
||||
}
|
||||
},
|
||||
attachCopyright: {
|
||||
js: {
|
||||
src: [
|
||||
'packages/node_modules/@node-red/editor-client/public/red/red.min.js',
|
||||
'packages/node_modules/@node-red/editor-client/public/red/main.min.js'
|
||||
'public/red/red.min.js'
|
||||
]
|
||||
},
|
||||
css: {
|
||||
src: [
|
||||
'packages/node_modules/@node-red/editor-client/public/red/style.min.css'
|
||||
'public/red/style.min.css'
|
||||
]
|
||||
}
|
||||
},
|
||||
clean: {
|
||||
build: {
|
||||
src: [
|
||||
"packages/node_modules/@node-red/editor-client/public/red",
|
||||
"packages/node_modules/@node-red/editor-client/public/index.html",
|
||||
"packages/node_modules/@node-red/editor-client/public/favicon.ico",
|
||||
"packages/node_modules/@node-red/editor-client/public/icons",
|
||||
"packages/node_modules/@node-red/editor-client/public/vendor",
|
||||
"packages/node_modules/@node-red/editor-client/public/types/node",
|
||||
"packages/node_modules/@node-red/editor-client/public/types/node-red",
|
||||
"public/red",
|
||||
"public/index.html",
|
||||
"public/favicon.ico",
|
||||
"public/icons",
|
||||
"public/vendor"
|
||||
]
|
||||
},
|
||||
release: {
|
||||
@ -326,36 +211,24 @@ module.exports = function(grunt) {
|
||||
watch: {
|
||||
js: {
|
||||
files: [
|
||||
'packages/node_modules/@node-red/editor-client/src/js/**/*.js'
|
||||
'editor/js/**/*.js'
|
||||
],
|
||||
tasks: ['copy:build','concat',/*'uglify',*/ 'attachCopyright:js']
|
||||
tasks: ['concat','uglify','attachCopyright:js']
|
||||
},
|
||||
sass: {
|
||||
files: [
|
||||
'packages/node_modules/@node-red/editor-client/src/sass/**/*.scss'
|
||||
'editor/sass/**/*.scss'
|
||||
],
|
||||
tasks: ['sass','attachCopyright:css']
|
||||
},
|
||||
json: {
|
||||
files: [
|
||||
'packages/node_modules/@node-red/nodes/locales/**/*.json',
|
||||
'packages/node_modules/@node-red/editor-client/locales/**/*.json',
|
||||
'packages/node_modules/@node-red/runtime/locales/**/*.json'
|
||||
'nodes/core/locales/en-US/messages.json',
|
||||
'red/api/locales/en-US/editor.json',
|
||||
'red/runtime/locales/en-US/runtime.json'
|
||||
],
|
||||
tasks: ['jsonlint:messages']
|
||||
},
|
||||
keymaps: {
|
||||
files: [
|
||||
'packages/node_modules/@node-red/editor-client/src/js/keymap.json'
|
||||
],
|
||||
tasks: ['jsonlint:keymaps','copy:build']
|
||||
},
|
||||
tours: {
|
||||
files: [
|
||||
'packages/node_modules/@node-red/editor-client/src/tours/**/*.js'
|
||||
],
|
||||
tasks: ['copy:build']
|
||||
},
|
||||
misc: {
|
||||
files: [
|
||||
'CHANGELOG.md'
|
||||
@ -367,13 +240,12 @@ module.exports = function(grunt) {
|
||||
nodemon: {
|
||||
/* uses .nodemonignore */
|
||||
dev: {
|
||||
script: 'packages/node_modules/node-red/red.js',
|
||||
script: 'red.js',
|
||||
options: {
|
||||
args: nodemonArgs,
|
||||
ext: 'js,html,json',
|
||||
watch: [
|
||||
'packages/node_modules',
|
||||
'!packages/node_modules/@node-red/editor-client'
|
||||
'red','nodes'
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -390,77 +262,62 @@ module.exports = function(grunt) {
|
||||
|
||||
copy: {
|
||||
build: {
|
||||
files:[
|
||||
{
|
||||
src: 'packages/node_modules/@node-red/editor-client/src/js/main.js',
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/main.js'
|
||||
},
|
||||
{
|
||||
src: 'packages/node_modules/@node-red/editor-client/src/js/keymap.json',
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/keymap.json'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src/images',
|
||||
src: '**',
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/images/'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src/vendor',
|
||||
src: [
|
||||
'ace/**',
|
||||
'jquery/css/base/**',
|
||||
'font-awesome/**',
|
||||
'monaco/dist/**',
|
||||
'monaco/types/extraLibs.js',
|
||||
'monaco/style.css',
|
||||
'monaco/monaco-bootstrap.js'
|
||||
],
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/vendor/'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src',
|
||||
src: [
|
||||
'types/node/**/*.ts',
|
||||
'types/node-red/*.ts',
|
||||
],
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src/icons',
|
||||
src: '**',
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/icons/'
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
src: ['packages/node_modules/@node-red/editor-client/src/index.html','packages/node_modules/@node-red/editor-client/src/favicon.ico'],
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/',
|
||||
flatten: true
|
||||
},
|
||||
{
|
||||
src: 'CHANGELOG.md',
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/about'
|
||||
},
|
||||
{
|
||||
src: 'CHANGELOG.md',
|
||||
dest: 'packages/node_modules/node-red/'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src/ace/bin/',
|
||||
src: '**',
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/vendor/ace/'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src/tours',
|
||||
src: '**',
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/tours/'
|
||||
}
|
||||
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
|
||||
},
|
||||
{
|
||||
src: 'CHANGELOG.md',
|
||||
dest: 'public/red/about'
|
||||
}
|
||||
]
|
||||
},
|
||||
release: {
|
||||
files: [{
|
||||
mode: true,
|
||||
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 %>')
|
||||
}]
|
||||
}
|
||||
},
|
||||
chmod: {
|
||||
@ -468,96 +325,20 @@ module.exports = function(grunt) {
|
||||
mode: '755'
|
||||
},
|
||||
release: {
|
||||
// Target-specific file/dir lists and/or options go here.
|
||||
src: [
|
||||
"packages/node_modules/@node-red/nodes/core/hardware/nrgpio",
|
||||
"packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-*sh"
|
||||
path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>/nodes/core/hardware/nrgpio*')
|
||||
]
|
||||
}
|
||||
},
|
||||
'npm-command': {
|
||||
options: {
|
||||
cmd: "pack",
|
||||
cwd: "<%= paths.dist %>/modules"
|
||||
},
|
||||
'node-red': { options: { args: [__dirname+'/packages/node_modules/node-red'] } },
|
||||
'@node-red/editor-api': { options: { args: [__dirname+'/packages/node_modules/@node-red/editor-api'] } },
|
||||
'@node-red/editor-client': { options: { args: [__dirname+'/packages/node_modules/@node-red/editor-client'] } },
|
||||
'@node-red/nodes': { options: { args: [__dirname+'/packages/node_modules/@node-red/nodes'] } },
|
||||
'@node-red/registry': { options: { args: [__dirname+'/packages/node_modules/@node-red/registry'] } },
|
||||
'@node-red/runtime': { options: { args: [__dirname+'/packages/node_modules/@node-red/runtime'] } },
|
||||
'@node-red/util': { options: { args: [__dirname+'/packages/node_modules/@node-red/util'] } }
|
||||
|
||||
|
||||
},
|
||||
mkdir: {
|
||||
release: {
|
||||
options: {
|
||||
create: ['<%= paths.dist %>/modules']
|
||||
},
|
||||
},
|
||||
},
|
||||
compress: {
|
||||
release: {
|
||||
options: {
|
||||
archive: '<%= paths.dist %>/node-red-<%= pkg.version %>.zip'
|
||||
},
|
||||
expand: true,
|
||||
cwd: 'packages/node_modules/',
|
||||
src: [
|
||||
'**',
|
||||
'!@node-red/editor-client/src/**'
|
||||
]
|
||||
}
|
||||
},
|
||||
jsdoc : {
|
||||
modules: {
|
||||
src: [
|
||||
'API.md',
|
||||
'packages/node_modules/node-red/lib/red.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/index.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/api/*.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/events.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/hooks.js',
|
||||
'packages/node_modules/@node-red/util/**/*.js',
|
||||
'packages/node_modules/@node-red/editor-api/lib/index.js',
|
||||
'packages/node_modules/@node-red/editor-api/lib/auth/index.js',
|
||||
'packages/node_modules/@node-red/registry/lib/index.js'
|
||||
],
|
||||
options: {
|
||||
destination: 'docs',
|
||||
configure: './jsdoc.json',
|
||||
fred: "hi there"
|
||||
}
|
||||
},
|
||||
_editor: {
|
||||
src: [
|
||||
'packages/node_modules/@node-red/editor-client/src/js'
|
||||
],
|
||||
options: {
|
||||
destination: 'packages/node_modules/@node-red/editor-client/docs',
|
||||
configure: './jsdoc.json'
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
jsdoc2md: {
|
||||
runtimeAPI: {
|
||||
options: {
|
||||
separators: true
|
||||
},
|
||||
src: [
|
||||
'packages/node_modules/@node-red/runtime/lib/index.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/api/*.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/events.js'
|
||||
],
|
||||
dest: 'packages/node_modules/@node-red/runtime/docs/api.md'
|
||||
},
|
||||
nodeREDUtil: {
|
||||
options: {
|
||||
separators: true
|
||||
},
|
||||
src: 'packages/node_modules/@node-red/util/**/*.js',
|
||||
dest: 'packages/node_modules/@node-red/util/docs/api.md'
|
||||
cwd: '<%= paths.dist %>/',
|
||||
src: ['node-red-<%= pkg.version %>/**']
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -570,42 +351,16 @@ module.exports = function(grunt) {
|
||||
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.loadNpmTasks('grunt-chmod');
|
||||
grunt.loadNpmTasks('grunt-jsonlint');
|
||||
if (fs.existsSync(path.join("node_modules", "grunt-webdriver"))) {
|
||||
grunt.loadNpmTasks('grunt-webdriver');
|
||||
}
|
||||
grunt.loadNpmTasks('grunt-jsdoc');
|
||||
grunt.loadNpmTasks('grunt-jsdoc-to-markdown');
|
||||
grunt.loadNpmTasks('grunt-npm-command');
|
||||
grunt.loadNpmTasks('grunt-mkdir');
|
||||
grunt.loadNpmTasks('grunt-simple-nyc');
|
||||
|
||||
grunt.registerMultiTask('nodemon', 'Runs a nodemon monitor of your node.js server.', function () {
|
||||
const nodemon = require('nodemon');
|
||||
this.async();
|
||||
const options = this.options();
|
||||
options.script = this.data.script;
|
||||
let callback;
|
||||
if (options.callback) {
|
||||
callback = options.callback;
|
||||
delete options.callback;
|
||||
} else {
|
||||
callback = function(nodemonApp) {
|
||||
nodemonApp.on('log', function (event) {
|
||||
console.log(event.colour);
|
||||
});
|
||||
};
|
||||
}
|
||||
callback(nodemon(options));
|
||||
});
|
||||
|
||||
grunt.registerMultiTask('attachCopyright', function() {
|
||||
var files = this.data.src;
|
||||
var copyright = "/**\n"+
|
||||
" * Copyright OpenJS Foundation and other contributors, https://openjsf.org/\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"+
|
||||
@ -621,7 +376,7 @@ module.exports = function(grunt) {
|
||||
" **/\n";
|
||||
|
||||
if (files) {
|
||||
for (var i=0; i<files.length; i++) {
|
||||
for (var i=0;i<files.length;i++) {
|
||||
var file = files[i];
|
||||
if (!grunt.file.exists(file)) {
|
||||
grunt.log.warn('File '+ file + ' not found');
|
||||
@ -642,38 +397,6 @@ module.exports = function(grunt) {
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask('verifyPackageDependencies', function() {
|
||||
var done = this.async();
|
||||
var verifyDependencies = require("./scripts/verify-package-dependencies.js");
|
||||
verifyDependencies().then(function(failures) {
|
||||
if (failures.length > 0) {
|
||||
failures.forEach(f => grunt.log.error(f));
|
||||
grunt.fail.fatal("Failed to verify package dependencies");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
grunt.registerTask('verifyUiTestDependencies', function() {
|
||||
if (!fs.existsSync(path.join("node_modules", "grunt-webdriver"))) {
|
||||
grunt.fail.fatal('You need to install the UI test dependencies first.\nUse the script in "scripts/install-ui-test-dependencies.sh"');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
grunt.registerTask('generatePublishScript',
|
||||
'Generates a script to publish build output to npm',
|
||||
function () {
|
||||
const done = this.async();
|
||||
const generatePublishScript = require("./scripts/generate-publish-script.js");
|
||||
generatePublishScript().then(function(output) {
|
||||
grunt.log.writeln(output);
|
||||
|
||||
const filePath = path.join(grunt.config.get('paths.dist'),"modules","publish.sh");
|
||||
grunt.file.write(filePath,output);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
grunt.registerTask('setDevEnv',
|
||||
'Sets NODE_ENV=development so non-minified assets are used',
|
||||
function () {
|
||||
@ -682,42 +405,23 @@ module.exports = function(grunt) {
|
||||
|
||||
grunt.registerTask('default',
|
||||
'Builds editor content then runs code style checks and unit tests on all components',
|
||||
['build','verifyPackageDependencies','jshint:editor','nyc:all']);
|
||||
|
||||
grunt.registerTask('no-coverage',
|
||||
'Builds editor content then runs code style checks and unit tests on all components without code coverage',
|
||||
['build','verifyPackageDependencies','jshint:editor','simplemocha:all']);
|
||||
|
||||
['build','test-core','test-editor','test-nodes']);
|
||||
|
||||
grunt.registerTask('test-core',
|
||||
'Runs code style check and unit tests on core runtime code',
|
||||
['build','nyc:core']);
|
||||
['jshint:core','simplemocha:core']);
|
||||
|
||||
grunt.registerTask('test-editor',
|
||||
'Runs code style check on editor code',
|
||||
['jshint:editor']);
|
||||
|
||||
if (!fs.existsSync(path.join("node_modules", "grunt-webdriver"))) {
|
||||
grunt.registerTask('test-ui',
|
||||
'Builds editor content then runs unit tests on editor ui',
|
||||
['verifyUiTestDependencies']);
|
||||
} else {
|
||||
grunt.registerTask('test-ui',
|
||||
'Builds editor content then runs unit tests on editor ui',
|
||||
['verifyUiTestDependencies','build','jshint:editor','webdriver:all']);
|
||||
}
|
||||
|
||||
grunt.registerTask('test-nodes',
|
||||
'Runs unit tests on core nodes',
|
||||
['build','nyc:nodes']);
|
||||
['simplemocha:nodes']);
|
||||
|
||||
grunt.registerTask('build',
|
||||
'Builds editor content',
|
||||
['clean:build','jsonlint','concat:build','concat:vendor','copy:build','uglify:build','sass:build','attachCopyright']);
|
||||
|
||||
grunt.registerTask('build-dev',
|
||||
'Developer mode: build dev version',
|
||||
['clean:build','concat:build','concat:vendor','copy:build','sass:build','setDevEnv']);
|
||||
['clean:build','concat:build','concat:vendor','uglify:build','sass:build','jsonlint:messages','copy:build','attachCopyright']);
|
||||
|
||||
grunt.registerTask('dev',
|
||||
'Developer mode: run node-red, watch for source changes and build/restart',
|
||||
@ -725,18 +429,6 @@ module.exports = function(grunt) {
|
||||
|
||||
grunt.registerTask('release',
|
||||
'Create distribution zip file',
|
||||
['build','verifyPackageDependencies','clean:release','mkdir:release','chmod:release','compress:release','pack-modules','generatePublishScript']);
|
||||
['build','clean:release','copy:release','chmod:release','compress:release']);
|
||||
|
||||
grunt.registerTask('pack-modules',
|
||||
'Create module pack files for release',
|
||||
['mkdir:release','npm-command']);
|
||||
|
||||
|
||||
grunt.registerTask('coverage',
|
||||
'Run Istanbul code test coverage task',
|
||||
['build','nyc:all']);
|
||||
|
||||
grunt.registerTask('docs',
|
||||
'Generates API documentation',
|
||||
['jsdoc']);
|
||||
};
|
||||
|
1
LICENSE
@ -1,4 +1,3 @@
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
48
README.md
@ -1,48 +1,54 @@
|
||||
# Node-RED
|
||||
|
||||
https://nodered.org
|
||||
http://nodered.org
|
||||
|
||||
[](https://github.com/node-red/node-red/actions?query=branch%3Amaster)
|
||||
[](https://travis-ci.org/node-red/node-red)
|
||||
[](https://coveralls.io/r/node-red/node-red?branch=master)
|
||||
|
||||
Low-code programming for event-driven applications.
|
||||
A visual tool for wiring the Internet of Things.
|
||||
|
||||

|
||||

|
||||
|
||||
## Quick Start
|
||||
|
||||
Check out https://nodered.org/docs/getting-started/ for full instructions on getting
|
||||
Check out http://nodered.org/docs/getting-started/ for full instructions on getting
|
||||
started.
|
||||
|
||||
1. `sudo npm install -g --unsafe-perm node-red`
|
||||
1. `sudo npm install -g node-red`
|
||||
2. `node-red`
|
||||
3. Open <http://localhost:1880>
|
||||
|
||||
## Getting Help
|
||||
|
||||
More documentation can be found [here](https://nodered.org/docs).
|
||||
More documentation can be found [here](http://nodered.org/docs).
|
||||
|
||||
For further help, or general discussion, please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
|
||||
For further help, or general discussion, please use the
|
||||
[mailing list](https://groups.google.com/forum/#!forum/node-red).
|
||||
|
||||
## Developers
|
||||
|
||||
If you want to run the latest code from git, here's how to get started:
|
||||
|
||||
1. Clone the code:
|
||||
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
|
||||
|
||||
2. Install the node-red dependencies
|
||||
3. Install the node-red dependencies
|
||||
|
||||
npm install
|
||||
|
||||
3. Build the code
|
||||
4. Build the code
|
||||
|
||||
npm run build
|
||||
grunt build
|
||||
|
||||
4. Run
|
||||
5. Run
|
||||
|
||||
npm start
|
||||
node red.js
|
||||
|
||||
## Contributing
|
||||
|
||||
@ -51,19 +57,17 @@ Before raising a pull-request, please read our
|
||||
|
||||
This project adheres to the [Contributor Covenant 1.4](http://contributor-covenant.org/version/1/4/).
|
||||
By participating, you are expected to uphold this code. Please report unacceptable
|
||||
behavior to any of the project's core team at team@nodered.org.
|
||||
behavior to any of the [project's core team](https://github.com/orgs/node-red/teams/core).
|
||||
|
||||
## Authors
|
||||
|
||||
Node-RED is a project of the [OpenJS Foundation](http://openjsf.org).
|
||||
Node-RED is a creation of [IBM Emerging Technology](http://ibm.com/blogs/et).
|
||||
|
||||
It is maintained by:
|
||||
|
||||
* Nick O'Leary [@knolleary](http://twitter.com/knolleary)
|
||||
* Dave Conway-Jones [@ceejay](http://twitter.com/ceejay)
|
||||
* And many others...
|
||||
* Nick O'Leary [@knolleary](http://twitter.com/knolleary)
|
||||
* Dave Conway-Jones [@ceejay](http://twitter.com/ceejay)
|
||||
|
||||
For more open-source projects from IBM, head over [here](http://ibm.github.io).
|
||||
|
||||
## Copyright and license
|
||||
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org under [the Apache 2.0 license](LICENSE).
|
||||
Copyright 2013, 2016 IBM Corp. under [the Apache 2.0 license](LICENSE).
|
||||
|
@ -1,5 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report any potential security issues to `team@nodered.org`. This will notify the core project team who will respond accordingly.
|
@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright JS Foundation and other contributors, http://js.foundation
|
||||
# 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.
|
||||
@ -29,16 +29,15 @@ do
|
||||
done
|
||||
|
||||
# Find the real location of this script
|
||||
CURRENT_PATH=$(pwd)
|
||||
SCRIPT_PATH=$(readlink -f "$0")
|
||||
while [ -h "${SCRIPT_PATH}" ]; do
|
||||
cd "$(dirname "${SCRIPT_PATH}")" || exit 1
|
||||
P=$(basename "${SCRIPT_PATH}")
|
||||
SCRIPT_PATH=$(readlink "${P}")
|
||||
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 || exit 1
|
||||
SCRIPT_PATH=$(pwd)
|
||||
cd "$CURRENT_PATH" || exit 1
|
||||
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
|
||||
SCRIPT_PATH="`pwd`";
|
||||
cd $CURRENT_PATH
|
||||
|
||||
# Run Node-RED
|
||||
exec /usr/bin/env node ${OPTIONS} "${SCRIPT_PATH}"/../red.js ${ARGS}
|
||||
/usr/bin/env node $OPTIONS $SCRIPT_PATH/../red.js $ARGS
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
BIN
editor/icons/alert.png
Normal file
After Width: | Height: | Size: 308 B |
BIN
editor/icons/arduino.png
Normal file
After Width: | Height: | Size: 603 B |
Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 393 B |
BIN
editor/icons/bluetooth.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
editor/icons/bridge-dash.png
Normal file
After Width: | Height: | Size: 508 B |
BIN
editor/icons/bridge.png
Normal file
After Width: | Height: | Size: 575 B |
BIN
editor/icons/comment.png
Normal file
After Width: | Height: | Size: 601 B |
BIN
editor/icons/db.png
Normal file
After Width: | Height: | Size: 459 B |
BIN
editor/icons/debug.png
Normal file
After Width: | Height: | Size: 218 B |
BIN
editor/icons/envelope.png
Normal file
After Width: | Height: | Size: 324 B |
BIN
editor/icons/feed.png
Normal file
After Width: | Height: | Size: 378 B |
BIN
editor/icons/file.png
Normal file
After Width: | Height: | Size: 255 B |
BIN
editor/icons/function.png
Normal file
After Width: | Height: | Size: 457 B |
BIN
editor/icons/hash.png
Normal file
After Width: | Height: | Size: 502 B |
BIN
editor/icons/inject.png
Normal file
After Width: | Height: | Size: 449 B |
BIN
editor/icons/join.png
Normal file
After Width: | Height: | Size: 253 B |
BIN
editor/icons/leveldb.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
editor/icons/light.png
Normal file
After Width: | Height: | Size: 639 B |
BIN
editor/icons/link-out.png
Normal file
After Width: | Height: | Size: 402 B |
BIN
editor/icons/mongodb.png
Normal file
After Width: | Height: | Size: 414 B |
BIN
editor/icons/mouse.png
Normal file
After Width: | Height: | Size: 671 B |
BIN
editor/icons/node-changed.png
Normal file
After Width: | Height: | Size: 386 B |
BIN
editor/icons/node-error.png
Normal file
After Width: | Height: | Size: 386 B |
BIN
editor/icons/parser-csv.png
Normal file
After Width: | Height: | Size: 413 B |
BIN
editor/icons/parser-html.png
Normal file
After Width: | Height: | Size: 393 B |
BIN
editor/icons/parser-json.png
Normal file
After Width: | Height: | Size: 467 B |
BIN
editor/icons/parser-xml.png
Normal file
After Width: | Height: | Size: 393 B |
BIN
editor/icons/range.png
Normal file
After Width: | Height: | Size: 360 B |
BIN
editor/icons/redis.png
Normal file
After Width: | Height: | Size: 736 B |
BIN
editor/icons/rpi.png
Normal file
After Width: | Height: | Size: 482 B |
BIN
editor/icons/serial.png
Normal file
After Width: | Height: | Size: 273 B |
BIN
editor/icons/split.png
Normal file
After Width: | Height: | Size: 256 B |
BIN
editor/icons/subflow.png
Normal file
After Width: | Height: | Size: 439 B |
BIN
editor/icons/swap.png
Normal file
After Width: | Height: | Size: 592 B |
BIN
editor/icons/switch.png
Normal file
After Width: | Height: | Size: 509 B |
BIN
editor/icons/template.png
Normal file
After Width: | Height: | Size: 488 B |
BIN
editor/icons/timer.png
Normal file
After Width: | Height: | Size: 628 B |
BIN
editor/icons/trigger.png
Normal file
After Width: | Height: | Size: 258 B |
BIN
editor/icons/twitter.png
Normal file
After Width: | Height: | Size: 404 B |
BIN
editor/icons/watch.png
Normal file
After Width: | Height: | Size: 591 B |
BIN
editor/icons/white-globe.png
Normal file
After Width: | Height: | Size: 707 B |
BIN
editor/images/deploy-flows-o.png
Normal file
After Width: | Height: | Size: 291 B |
BIN
editor/images/deploy-flows.png
Normal file
After Width: | Height: | Size: 386 B |
BIN
editor/images/deploy-full-o.png
Normal file
After Width: | Height: | Size: 289 B |
BIN
editor/images/deploy-full.png
Normal file
After Width: | Height: | Size: 368 B |
BIN
editor/images/deploy-nodes-o.png
Normal file
After Width: | Height: | Size: 290 B |
BIN
editor/images/deploy-nodes.png
Normal file
After Width: | Height: | Size: 392 B |
BIN
editor/images/grip.png
Normal file
After Width: | Height: | Size: 192 B |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
BIN
editor/images/node-red.png
Normal file
After Width: | Height: | Size: 1019 B |
BIN
editor/images/pw_maze_white.png
Normal file
After Width: | Height: | Size: 600 B |
BIN
editor/images/subflow_tab.png
Normal file
After Width: | Height: | Size: 410 B |
BIN
editor/images/typedInput/09.png
Normal file
After Width: | Height: | Size: 638 B |
BIN
editor/images/typedInput/az.png
Normal file
After Width: | Height: | Size: 546 B |
BIN
editor/images/typedInput/bool.png
Normal file
After Width: | Height: | Size: 646 B |
BIN
editor/images/typedInput/json.png
Normal file
After Width: | Height: | Size: 588 B |
BIN
editor/images/typedInput/re.png
Normal file
After Width: | Height: | Size: 502 B |
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
* Copyright 2014, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -26,35 +26,16 @@ RED.comms = (function() {
|
||||
var reconnectAttempts = 0;
|
||||
var active = false;
|
||||
|
||||
RED.events.on('login', function(username) {
|
||||
// User has logged in
|
||||
// Need to upgrade the connection to be authenticated
|
||||
if (ws && ws.readyState == 1) {
|
||||
const auth_tokens = RED.settings.get("auth-tokens");
|
||||
ws.send(JSON.stringify({auth:auth_tokens.access_token}))
|
||||
}
|
||||
})
|
||||
|
||||
function connectWS() {
|
||||
active = true;
|
||||
var wspath;
|
||||
|
||||
if (RED.settings.apiRootUrl) {
|
||||
var m = /^(https?):\/\/(.*)$/.exec(RED.settings.apiRootUrl);
|
||||
if (m) {
|
||||
console.log(m);
|
||||
wspath = "ws"+(m[1]==="https"?"s":"")+"://"+m[2]+"comms";
|
||||
}
|
||||
} else {
|
||||
var path = location.hostname;
|
||||
var port = location.port;
|
||||
if (port.length !== 0) {
|
||||
path = path+":"+port;
|
||||
}
|
||||
path = path+document.location.pathname;
|
||||
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
|
||||
wspath = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
|
||||
var path = location.hostname;
|
||||
var port = location.port;
|
||||
if (port.length !== 0) {
|
||||
path = path+":"+port;
|
||||
}
|
||||
path = path+document.location.pathname;
|
||||
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
|
||||
path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
|
||||
|
||||
var auth_tokens = RED.settings.get("auth-tokens");
|
||||
pendingAuth = (auth_tokens!=null);
|
||||
@ -65,10 +46,9 @@ RED.comms = (function() {
|
||||
ws.send(JSON.stringify({subscribe:t}));
|
||||
}
|
||||
}
|
||||
emit('connect')
|
||||
}
|
||||
|
||||
ws = new WebSocket(wspath);
|
||||
ws = new WebSocket(path);
|
||||
ws.onopen = function() {
|
||||
reconnectAttempts = 0;
|
||||
if (errornotification) {
|
||||
@ -84,41 +64,27 @@ RED.comms = (function() {
|
||||
}
|
||||
}
|
||||
ws.onmessage = function(event) {
|
||||
var message = JSON.parse(event.data);
|
||||
if (message.auth) {
|
||||
if (pendingAuth) {
|
||||
if (message.auth === "ok") {
|
||||
pendingAuth = false;
|
||||
completeConnection();
|
||||
} else if (message.auth === "fail") {
|
||||
// anything else is an error...
|
||||
active = false;
|
||||
RED.user.login({updateMenu:true},function() {
|
||||
connectWS();
|
||||
})
|
||||
}
|
||||
} else if (message.auth === "fail") {
|
||||
// Our current session has expired
|
||||
var msg = JSON.parse(event.data);
|
||||
if (pendingAuth && msg.auth) {
|
||||
if (msg.auth === "ok") {
|
||||
pendingAuth = false;
|
||||
completeConnection();
|
||||
} else if (msg.auth === "fail") {
|
||||
// anything else is an error...
|
||||
active = false;
|
||||
RED.user.login({updateMenu:true},function() {
|
||||
connectWS();
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Otherwise, 'message' is an array of actual comms messages
|
||||
for (var m = 0; m < message.length; m++) {
|
||||
var msg = message[m];
|
||||
if (msg.topic) {
|
||||
for (var t in subscriptions) {
|
||||
if (subscriptions.hasOwnProperty(t)) {
|
||||
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
|
||||
if (re.test(msg.topic)) {
|
||||
var subscribers = subscriptions[t];
|
||||
if (subscribers) {
|
||||
for (var i=0;i<subscribers.length;i++) {
|
||||
subscribers[i](msg.topic,msg.data);
|
||||
}
|
||||
}
|
||||
} else if (msg.topic) {
|
||||
for (var t in subscriptions) {
|
||||
if (subscriptions.hasOwnProperty(t)) {
|
||||
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
|
||||
if (re.test(msg.topic)) {
|
||||
var subscribers = subscriptions[t];
|
||||
if (subscribers) {
|
||||
for (var i=0;i<subscribers.length;i++) {
|
||||
subscribers[i](msg.topic,msg.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -152,10 +118,10 @@ RED.comms = (function() {
|
||||
connectWS();
|
||||
} else {
|
||||
var msg = RED._("notification.errors.lostConnectionReconnect",{time: connectCountdown})+' <a href="#">'+ RED._("notification.errors.lostConnectionTry")+'</a>';
|
||||
errornotification.update(msg,{silent:true});
|
||||
$(errornotification).find("a").on("click", function(e) {
|
||||
errornotification.update(msg);
|
||||
$(errornotification).find("a").click(function(e) {
|
||||
e.preventDefault();
|
||||
errornotification.update(RED._("notification.errors.lostConnection"),{silent:true});
|
||||
errornotification.update(RED._("notification.errors.lostConnection"));
|
||||
clearInterval(connectCountdownTimer);
|
||||
connectWS();
|
||||
})
|
||||
@ -190,53 +156,9 @@ RED.comms = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
function send(topic, msg) {
|
||||
if (ws && ws.readyState == 1) {
|
||||
ws.send(JSON.stringify({
|
||||
topic,
|
||||
data: msg
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const eventHandlers = {};
|
||||
function on(evt,func) {
|
||||
eventHandlers[evt] = eventHandlers[evt]||[];
|
||||
eventHandlers[evt].push(func);
|
||||
}
|
||||
function off(evt,func) {
|
||||
const handler = eventHandlers[evt];
|
||||
if (handler) {
|
||||
for (let i=0;i<handler.length;i++) {
|
||||
if (handler[i] === func) {
|
||||
handler.splice(i,1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function emit() {
|
||||
const evt = arguments[0]
|
||||
const args = Array.prototype.slice.call(arguments,1);
|
||||
if (eventHandlers[evt]) {
|
||||
let cpyHandlers = [...eventHandlers[evt]];
|
||||
for (let i=0;i<cpyHandlers.length;i++) {
|
||||
try {
|
||||
cpyHandlers[i].apply(null, args);
|
||||
} catch(err) {
|
||||
console.warn("RED.comms.emit error: ["+evt+"] "+(err.toString()));
|
||||
console.warn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
connect: connectWS,
|
||||
subscribe: subscribe,
|
||||
unsubscribe:unsubscribe,
|
||||
on,
|
||||
off,
|
||||
send
|
||||
unsubscribe:unsubscribe
|
||||
}
|
||||
})();
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
* 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.
|
||||
@ -32,23 +32,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
function emit() {
|
||||
var evt = arguments[0]
|
||||
var args = Array.prototype.slice.call(arguments,1);
|
||||
if (RED.events.DEBUG) {
|
||||
console.warn(evt,args);
|
||||
}
|
||||
function emit(evt,arg) {
|
||||
if (handlers[evt]) {
|
||||
let cpyHandlers = [...handlers[evt]];
|
||||
|
||||
for (var i=0;i<cpyHandlers.length;i++) {
|
||||
try {
|
||||
cpyHandlers[i].apply(null, args);
|
||||
} catch(err) {
|
||||
console.warn("RED.events.emit error: ["+evt+"] "+(err.toString()));
|
||||
console.warn(err);
|
||||
}
|
||||
for (var i=0;i<handlers[evt].length;i++) {
|
||||
handlers[evt][i](arg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return {
|
300
editor/js/history.js
Normal file
@ -0,0 +1,300 @@
|
||||
/**
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
RED.history = (function() {
|
||||
var undo_history = [];
|
||||
|
||||
return {
|
||||
//TODO: this function is a placeholder until there is a 'save' event that can be listened to
|
||||
markAllDirty: function() {
|
||||
for (var i=0;i<undo_history.length;i++) {
|
||||
undo_history[i].dirty = true;
|
||||
}
|
||||
},
|
||||
list: function() {
|
||||
return undo_history
|
||||
},
|
||||
depth: function() {
|
||||
return undo_history.length;
|
||||
},
|
||||
push: function(ev) {
|
||||
undo_history.push(ev);
|
||||
},
|
||||
pop: function() {
|
||||
var ev = undo_history.pop();
|
||||
var i;
|
||||
var node;
|
||||
var subflow;
|
||||
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]);
|
||||
}
|
||||
}
|
||||
if (ev.links) {
|
||||
for (i=0;i<ev.links.length;i++) {
|
||||
RED.nodes.removeLink(ev.links[i]);
|
||||
}
|
||||
}
|
||||
if (ev.workspaces) {
|
||||
for (i=0;i<ev.workspaces.length;i++) {
|
||||
RED.nodes.removeWorkspace(ev.workspaces[i].id);
|
||||
RED.workspaces.remove(ev.workspaces[i]);
|
||||
}
|
||||
}
|
||||
if (ev.subflows) {
|
||||
for (i=0;i<ev.subflows.length;i++) {
|
||||
RED.nodes.removeSubflow(ev.subflows[i]);
|
||||
RED.workspaces.remove(ev.subflows[i]);
|
||||
}
|
||||
}
|
||||
if (ev.subflow) {
|
||||
if (ev.subflow.instances) {
|
||||
ev.subflow.instances.forEach(function(n) {
|
||||
var node = RED.nodes.node(n.id);
|
||||
if (node) {
|
||||
node.changed = n.changed;
|
||||
node.dirty = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (ev.subflow.hasOwnProperty('changed')) {
|
||||
subflow = RED.nodes.subflow(ev.subflow.id);
|
||||
if (subflow) {
|
||||
subflow.changed = ev.subflow.changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ev.removedLinks) {
|
||||
for (i=0;i<ev.removedLinks.length;i++) {
|
||||
RED.nodes.addLink(ev.removedLinks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (ev.t == "delete") {
|
||||
if (ev.workspaces) {
|
||||
for (i=0;i<ev.workspaces.length;i++) {
|
||||
RED.nodes.addWorkspace(ev.workspaces[i]);
|
||||
RED.workspaces.add(ev.workspaces[i]);
|
||||
}
|
||||
}
|
||||
if (ev.subflow && ev.subflow.subflow) {
|
||||
RED.nodes.addSubflow(ev.subflow.subflow);
|
||||
}
|
||||
if (ev.subflowInputs && ev.subflowInputs.length > 0) {
|
||||
subflow = RED.nodes.subflow(ev.subflowInputs[0].z);
|
||||
subflow.in.push(ev.subflowInputs[0]);
|
||||
subflow.in[0].dirty = true;
|
||||
}
|
||||
if (ev.subflowOutputs && ev.subflowOutputs.length > 0) {
|
||||
subflow = RED.nodes.subflow(ev.subflowOutputs[0].z);
|
||||
ev.subflowOutputs.sort(function(a,b) { return a.i-b.i});
|
||||
for (i=0;i<ev.subflowOutputs.length;i++) {
|
||||
var output = ev.subflowOutputs[i];
|
||||
subflow.out.splice(output.i,0,output);
|
||||
for (var j=output.i+1;j<subflow.out.length;j++) {
|
||||
subflow.out[j].i++;
|
||||
subflow.out[j].dirty = true;
|
||||
}
|
||||
RED.nodes.eachLink(function(l) {
|
||||
if (l.source.type == "subflow:"+subflow.id) {
|
||||
if (l.sourcePort >= output.i) {
|
||||
l.sourcePort++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (ev.subflow && ev.subflow.hasOwnProperty('instances')) {
|
||||
ev.subflow.instances.forEach(function(n) {
|
||||
var node = RED.nodes.node(n.id);
|
||||
if (node) {
|
||||
node.changed = n.changed;
|
||||
node.dirty = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (subflow) {
|
||||
RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) {
|
||||
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) {
|
||||
for (i=0;i<ev.links.length;i++) {
|
||||
RED.nodes.addLink(ev.links[i]);
|
||||
}
|
||||
}
|
||||
if (ev.changes) {
|
||||
for (i in ev.changes) {
|
||||
if (ev.changes.hasOwnProperty(i)) {
|
||||
node = RED.nodes.node(i);
|
||||
if (node) {
|
||||
for (var d in ev.changes[i]) {
|
||||
if (ev.changes[i].hasOwnProperty(d)) {
|
||||
node[d] = ev.changes[i][d];
|
||||
}
|
||||
}
|
||||
node.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else if (ev.t == "move") {
|
||||
for (i=0;i<ev.nodes.length;i++) {
|
||||
var n = ev.nodes[i];
|
||||
n.n.x = n.ox;
|
||||
n.n.y = n.oy;
|
||||
n.n.dirty = true;
|
||||
}
|
||||
// A move could have caused a link splice
|
||||
if (ev.links) {
|
||||
for (i=0;i<ev.links.length;i++) {
|
||||
RED.nodes.removeLink(ev.links[i]);
|
||||
}
|
||||
}
|
||||
if (ev.removedLinks) {
|
||||
for (i=0;i<ev.removedLinks.length;i++) {
|
||||
RED.nodes.addLink(ev.removedLinks[i]);
|
||||
}
|
||||
}
|
||||
} else if (ev.t == "edit") {
|
||||
for (i in ev.changes) {
|
||||
if (ev.changes.hasOwnProperty(i)) {
|
||||
if (ev.node._def.defaults[i].type) {
|
||||
// This is a config node property
|
||||
var currentConfigNode = RED.nodes.node(ev.node[i]);
|
||||
if (currentConfigNode) {
|
||||
currentConfigNode.users.splice(currentConfigNode.users.indexOf(ev.node),1);
|
||||
}
|
||||
var newConfigNode = RED.nodes.node(ev.changes[i]);
|
||||
if (newConfigNode) {
|
||||
newConfigNode.users.push(ev.node);
|
||||
}
|
||||
}
|
||||
ev.node[i] = ev.changes[i];
|
||||
}
|
||||
}
|
||||
if (ev.subflow) {
|
||||
if (ev.subflow.hasOwnProperty('inputCount')) {
|
||||
if (ev.node.in.length > ev.subflow.inputCount) {
|
||||
ev.node.in.splice(ev.subflow.inputCount);
|
||||
} else if (ev.subflow.inputs.length > 0) {
|
||||
ev.node.in = ev.node.in.concat(ev.subflow.inputs);
|
||||
}
|
||||
}
|
||||
if (ev.subflow.hasOwnProperty('outputCount')) {
|
||||
if (ev.node.out.length > ev.subflow.outputCount) {
|
||||
ev.node.out.splice(ev.subflow.outputCount);
|
||||
} else if (ev.subflow.outputs.length > 0) {
|
||||
ev.node.out = ev.node.out.concat(ev.subflow.outputs);
|
||||
}
|
||||
}
|
||||
if (ev.subflow.hasOwnProperty('instances')) {
|
||||
ev.subflow.instances.forEach(function(n) {
|
||||
var node = RED.nodes.node(n.id);
|
||||
if (node) {
|
||||
node.changed = n.changed;
|
||||
node.dirty = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
|
||||
n.inputs = ev.node.in.length;
|
||||
n.outputs = ev.node.out.length;
|
||||
RED.editor.updateNodeProperties(n);
|
||||
});
|
||||
|
||||
if (ev.node.type === 'subflow') {
|
||||
$("#menu-item-workspace-menu-"+ev.node.id.replace(".","-")).text(ev.node.name);
|
||||
}
|
||||
} else {
|
||||
RED.editor.updateNodeProperties(ev.node);
|
||||
RED.editor.validateNode(ev.node);
|
||||
}
|
||||
if (ev.links) {
|
||||
for (i=0;i<ev.links.length;i++) {
|
||||
RED.nodes.addLink(ev.links[i]);
|
||||
}
|
||||
}
|
||||
ev.node.dirty = true;
|
||||
ev.node.changed = ev.changed;
|
||||
} else if (ev.t == "createSubflow") {
|
||||
if (ev.nodes) {
|
||||
RED.nodes.filterNodes({z:ev.subflow.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]);
|
||||
}
|
||||
}
|
||||
if (ev.links) {
|
||||
for (i=0;i<ev.links.length;i++) {
|
||||
RED.nodes.removeLink(ev.links[i]);
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.removeSubflow(ev.subflow.subflow);
|
||||
RED.workspaces.remove(ev.subflow.subflow);
|
||||
|
||||
if (ev.removedLinks) {
|
||||
for (i=0;i<ev.removedLinks.length;i++) {
|
||||
RED.nodes.addLink(ev.removedLinks[i]);
|
||||
}
|
||||
}
|
||||
} else if (ev.t == "reorder") {
|
||||
if (ev.order) {
|
||||
RED.workspaces.order(ev.order);
|
||||
}
|
||||
}
|
||||
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();
|
||||
RED.workspaces.refresh();
|
||||
RED.sidebar.config.refresh();
|
||||
}
|
||||
},
|
||||
peek: function() {
|
||||
return undo_history[undo_history.length-1];
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
43
editor/js/i18n.js
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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.i18n = (function() {
|
||||
|
||||
return {
|
||||
init: function(done) {
|
||||
i18n.init({
|
||||
resGetPath: 'locales/__ns__',
|
||||
dynamicLoad: false,
|
||||
load:'current',
|
||||
ns: {
|
||||
namespaces: ["editor","node-red"],
|
||||
defaultNs: "editor"
|
||||
},
|
||||
fallbackLng: ['en-US'],
|
||||
useCookie: false
|
||||
},function() {
|
||||
done();
|
||||
});
|
||||
RED["_"] = function() {
|
||||
return i18n.t.apply(null,arguments);
|
||||
}
|
||||
|
||||
},
|
||||
loadCatalog: function(namespace,done) {
|
||||
i18n.loadNamespace(namespace,done);
|
||||
}
|
||||
}
|
||||
})();
|
250
editor/js/main.js
Normal file
@ -0,0 +1,250 @@
|
||||
/**
|
||||
* 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 loadNodeList() {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"application/json"
|
||||
},
|
||||
cache: false,
|
||||
url: 'nodes',
|
||||
success: function(data) {
|
||||
RED.nodes.setNodeList(data);
|
||||
|
||||
var nsCount = 0;
|
||||
for(var i=0;i<data.length;i++) {
|
||||
var ns = data[i];
|
||||
if (ns.module != "node-red") {
|
||||
nsCount++;
|
||||
RED.i18n.loadCatalog(ns.id, function() {
|
||||
nsCount--;
|
||||
if (nsCount === 0) {
|
||||
loadNodes();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (nsCount === 0) {
|
||||
loadNodes();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadNodes() {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"text/html"
|
||||
},
|
||||
cache: false,
|
||||
url: 'nodes',
|
||||
success: function(data) {
|
||||
$("body").append(data);
|
||||
$("body").i18n();
|
||||
|
||||
|
||||
$(".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) {
|
||||
if (msg.text) {
|
||||
msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()});
|
||||
}
|
||||
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);
|
||||
RED.i18n.loadCatalog(id, function() {
|
||||
$.get('nodes/'+id, function(data) {
|
||||
$("body").append(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (addedTypes.length) {
|
||||
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+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(RED._("palette.event.nodeRemoved", {count:m.types.length})+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(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
|
||||
} else {
|
||||
$.get('nodes/'+msg.id, function(data) {
|
||||
$("body").append(data);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+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(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
|
||||
}
|
||||
}
|
||||
// Refresh flow library to ensure any examples are updated
|
||||
RED.library.loadFlowLibrary();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showAbout() {
|
||||
$.get('red/about', function(data) {
|
||||
var aboutHeader = '<div style="text-align:center;">'+
|
||||
'<img width="50px" src="red/images/node-red-icon.svg" />'+
|
||||
'</div>';
|
||||
|
||||
RED.sidebar.info.set(aboutHeader+marked(data));
|
||||
RED.sidebar.info.show();
|
||||
});
|
||||
}
|
||||
|
||||
var statusEnabled = false;
|
||||
function toggleStatus(state) {
|
||||
statusEnabled = state;
|
||||
RED.view.status(statusEnabled);
|
||||
}
|
||||
|
||||
function loadEditor() {
|
||||
RED.menu.init({id:"btn-sidemenu",
|
||||
options: [
|
||||
{id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
|
||||
{id:"menu-item-view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:RED.view.toggleShowGrid},
|
||||
{id:"menu-item-view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:RED.view.toggleSnapGrid},
|
||||
{id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true},
|
||||
null,
|
||||
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true}
|
||||
]},
|
||||
null,
|
||||
{id:"menu-item-import",label:RED._("menu.label.import"),options:[
|
||||
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:RED.clipboard.import},
|
||||
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
|
||||
]},
|
||||
{id:"menu-item-export",label:RED._("menu.label.export"),disabled:true,options:[
|
||||
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:RED.clipboard.export},
|
||||
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:RED.library.export}
|
||||
]},
|
||||
null,
|
||||
{id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:function(){}},
|
||||
{id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
|
||||
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:RED.workspaces.add},
|
||||
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:RED.workspaces.edit},
|
||||
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:RED.workspaces.remove},
|
||||
null
|
||||
]},
|
||||
{id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
|
||||
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow},
|
||||
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow},
|
||||
]},
|
||||
null,
|
||||
{id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),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")
|
||||
},
|
||||
{id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: showAbout }
|
||||
]
|
||||
});
|
||||
|
||||
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.editor.init();
|
||||
|
||||
RED.deploy.init(RED.settings.theme("deployButton",null));
|
||||
|
||||
RED.keyboard.add("workspace", /* ? */ 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.i18n.init(function() {
|
||||
RED.settings.init(loadEditor);
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
};
|
||||
})();
|
1073
editor/js/nodes.js
Normal file
151
editor/js/settings.js
Normal file
@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright 2014 IBM, Antoine Aflalo
|
||||
*
|
||||
* 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.settings = (function () {
|
||||
|
||||
var loadedSettings = {};
|
||||
|
||||
var hasLocalStorage = function () {
|
||||
try {
|
||||
return 'localStorage' in window && window['localStorage'] !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var set = function (key, value) {
|
||||
if (!hasLocalStorage()) {
|
||||
return;
|
||||
}
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
};
|
||||
|
||||
/**
|
||||
* If the key is not set in the localStorage it returns <i>undefined</i>
|
||||
* Else return the JSON parsed value
|
||||
* @param key
|
||||
* @returns {*}
|
||||
*/
|
||||
var get = function (key) {
|
||||
if (!hasLocalStorage()) {
|
||||
return undefined;
|
||||
}
|
||||
return JSON.parse(localStorage.getItem(key));
|
||||
};
|
||||
|
||||
var remove = function (key) {
|
||||
if (!hasLocalStorage()) {
|
||||
return;
|
||||
}
|
||||
localStorage.removeItem(key);
|
||||
};
|
||||
|
||||
var setProperties = function(data) {
|
||||
for (var prop in loadedSettings) {
|
||||
if (loadedSettings.hasOwnProperty(prop) && RED.settings.hasOwnProperty(prop)) {
|
||||
delete RED.settings[prop];
|
||||
}
|
||||
}
|
||||
for (prop in data) {
|
||||
if (data.hasOwnProperty(prop)) {
|
||||
RED.settings[prop] = data[prop];
|
||||
}
|
||||
}
|
||||
loadedSettings = data;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
load(done);
|
||||
}
|
||||
|
||||
var load = function(done) {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
},
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
url: 'settings',
|
||||
success: function (data) {
|
||||
setProperties(data);
|
||||
if (RED.settings.user && RED.settings.user.anonymous) {
|
||||
RED.settings.remove("auth-tokens");
|
||||
}
|
||||
console.log("Node-RED: " + data.version);
|
||||
done();
|
||||
},
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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]];
|
||||
}
|
||||
if (v === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
return v;
|
||||
} catch(err) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
load: load,
|
||||
set: set,
|
||||
get: get,
|
||||
remove: remove,
|
||||
theme: theme
|
||||
}
|
||||
})
|
||||
();
|
190
editor/js/ui/clipboard.js
Normal file
@ -0,0 +1,190 @@
|
||||
/**
|
||||
* 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;
|
||||
var dialogContainer;
|
||||
var exportNodesDialog;
|
||||
var importNodesDialog;
|
||||
|
||||
function setupDialogs() {
|
||||
dialog = $('<div id="clipboard-dialog" class="hide node-red-dialog"><form class="dialog-form form-horizontal"></form></div>')
|
||||
.appendTo("body")
|
||||
.dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
resizable: false,
|
||||
buttons: [
|
||||
{
|
||||
id: "clipboard-dialog-cancel",
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "clipboard-dialog-close",
|
||||
class: "primary",
|
||||
text: RED._("common.label.close"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "clipboard-dialog-ok",
|
||||
class: "primary",
|
||||
text: RED._("common.label.import"),
|
||||
click: function() {
|
||||
RED.view.importNodes($("#clipboard-import").val());
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
||||
},
|
||||
close: function(e) {
|
||||
}
|
||||
});
|
||||
|
||||
dialogContainer = dialog.children(".dialog-form");
|
||||
|
||||
exportNodesDialog = '<div class="form-row">'+
|
||||
'<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> '+RED._("clipboard.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">'+
|
||||
RED._("clipboard.selectNodes")+
|
||||
'</div>';
|
||||
|
||||
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="'+
|
||||
RED._("clipboard.pasteNodes")+
|
||||
'"></textarea>'+
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function validateImport() {
|
||||
var importInput = $("#clipboard-import");
|
||||
var v = importInput.val();
|
||||
v = v.substring(v.indexOf('['),v.lastIndexOf(']')+1);
|
||||
try {
|
||||
JSON.parse(v);
|
||||
importInput.removeClass("input-error");
|
||||
importInput.val(v);
|
||||
$("#clipboard-dialog-ok").button("enable");
|
||||
} catch(err) {
|
||||
if (v !== "") {
|
||||
importInput.addClass("input-error");
|
||||
}
|
||||
$("#clipboard-dialog-ok").button("disable");
|
||||
}
|
||||
}
|
||||
|
||||
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(validateImport);
|
||||
$("#clipboard-import").on('paste',function() { setTimeout(validateImport,10)});
|
||||
|
||||
dialog.dialog("option","title",RED._("clipboard.importNodes")).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);
|
||||
if (RED.settings.flowFilePretty) {
|
||||
nns = JSON.stringify(nns,null,4);
|
||||
} else {
|
||||
nns = JSON.stringify(nns);
|
||||
}
|
||||
$("#clipboard-export")
|
||||
.val(nns)
|
||||
.focus(function() {
|
||||
var textarea = $(this);
|
||||
textarea.select();
|
||||
textarea.mouseup(function() {
|
||||
textarea.unbind("mouseup");
|
||||
return false;
|
||||
})
|
||||
});
|
||||
dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" );
|
||||
}
|
||||
}
|
||||
|
||||
function hideDropTarget() {
|
||||
$("#dropTarget").hide();
|
||||
RED.keyboard.remove(/* ESCAPE */ 27);
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
setupDialogs();
|
||||
RED.events.on("view: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("workspace", /* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();});
|
||||
RED.keyboard.add("workspace", /* 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();
|
||||
data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
|
||||
RED.view.importNodes(data);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
},
|
||||
import: importNodes,
|
||||
export: exportNodes
|
||||
}
|
||||
})();
|
289
editor/js/ui/deploy.js
Normal file
@ -0,0 +1,289 @@
|
||||
/**
|
||||
* Copyright 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
RED.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>'+RED._("deploy.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:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
|
||||
{id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.png",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}},
|
||||
{id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
|
||||
]
|
||||
});
|
||||
} else if (type == "simple") {
|
||||
var label = options.label || RED._("deploy.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: RED._("deploy.confirm.button.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: RED._("deploy.confirm.button.confirm"),
|
||||
class: "primary",
|
||||
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" );
|
||||
}
|
||||
}
|
||||
],
|
||||
create: function() {
|
||||
$("#node-dialog-confirm-deploy").parent().find("div.ui-dialog-buttonpane")
|
||||
.prepend('<div style="height:0; vertical-align: middle; display:inline-block; margin-top: 13px; float:left;">'+
|
||||
'<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.events.on('nodes:change',function(state) {
|
||||
if (state.dirty) {
|
||||
window.onbeforeunload = function() {
|
||||
return RED._("deploy.confirm.undeployedChanges");
|
||||
}
|
||||
$("#btn-deploy").removeClass("disabled");
|
||||
} else {
|
||||
window.onbeforeunload = null;
|
||||
$("#btn-deploy").addClass("disabled");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getNodeInfo(node) {
|
||||
var tabLabel = "";
|
||||
if (node.z) {
|
||||
var tab = RED.nodes.workspace(node.z);
|
||||
if (!tab) {
|
||||
tab = RED.nodes.subflow(node.z);
|
||||
tabLabel = tab.name;
|
||||
} else {
|
||||
tabLabel = tab.label;
|
||||
}
|
||||
}
|
||||
var label = "";
|
||||
if (typeof node._def.label == "function") {
|
||||
label = node._def.label.call(node);
|
||||
} else {
|
||||
label = node._def.label;
|
||||
}
|
||||
label = label || node.id;
|
||||
return {tab:tabLabel,type:node.type,label:label};
|
||||
}
|
||||
function sortNodeInfo(A,B) {
|
||||
if (A.tab < B.tab) { return -1;}
|
||||
if (A.tab > B.tab) { return 1;}
|
||||
if (A.type < B.type) { return -1;}
|
||||
if (A.type > B.type) { return 1;}
|
||||
if (A.name < B.name) { return -1;}
|
||||
if (A.name > B.name) { return 1;}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 = [];
|
||||
var invalidNodes = [];
|
||||
|
||||
RED.nodes.eachNode(function(node) {
|
||||
hasInvalid = hasInvalid || !node.valid;
|
||||
if (!node.valid) {
|
||||
invalidNodes.push(getNodeInfo(node));
|
||||
}
|
||||
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) {
|
||||
unusedConfigNodes.push(getNodeInfo(node));
|
||||
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();
|
||||
invalidNodes.sort(sortNodeInfo);
|
||||
$( "#node-dialog-confirm-deploy-invalid-list" )
|
||||
.html("<li>"+invalidNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"}).join("</li><li>")+"</li>");
|
||||
|
||||
} else if (hasUnusedConfig && !ignoreDeployWarnings.unusedConfig) {
|
||||
// showWarning = true;
|
||||
// $( "#node-dialog-confirm-deploy-type" ).val("unusedConfig");
|
||||
// $( "#node-dialog-confirm-deploy-unused" ).show();
|
||||
//
|
||||
// unusedConfigNodes.sort(sortNodeInfo);
|
||||
// $( "#node-dialog-confirm-deploy-unused-list" )
|
||||
// .html("<li>"+unusedConfigNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"}).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) {
|
||||
if (hasUnusedConfig) {
|
||||
RED.notify(
|
||||
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+
|
||||
'<p>'+RED._("deploy.unusedConfigNodes")+' <a href="#" onclick="RED.sidebar.config.show(true); return false;">'+RED._("deploy.unusedConfigNodesLink")+'</a></p>',"success",false,6000);
|
||||
} else {
|
||||
RED.notify(RED._("deploy.successfulDeploy"),"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();
|
||||
RED.events.emit("deploy");
|
||||
}).fail(function(xhr,textStatus,err) {
|
||||
RED.nodes.dirty(true);
|
||||
if (xhr.status === 401) {
|
||||
RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error");
|
||||
} else if (xhr.responseText) {
|
||||
RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error");
|
||||
} else {
|
||||
RED.notify(RED._("deploy.deployFailed",{message:RED._("deploy.errors.noResponse")}),"error");
|
||||
}
|
||||
}).always(function() {
|
||||
$("#btn-deploy-icon").removeClass('spinner');
|
||||
$("#btn-deploy-icon").addClass('fa-download');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: init
|
||||
}
|
||||
})();
|
191
editor/js/ui/editableList.js
Normal file
@ -0,0 +1,191 @@
|
||||
/**
|
||||
* Copyright 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
(function($) {
|
||||
|
||||
/**
|
||||
* options:
|
||||
* - addButton : boolean|string - text for add label, default 'add'
|
||||
* - height : number|'auto'
|
||||
* - resize : function - called when list as a whole is resized
|
||||
* - resizeItem : function(item) - called to resize individual item
|
||||
* - sortable : boolean|string - string is the css selector for handle
|
||||
* - sortItems : function(items) - when order of items changes
|
||||
* - connectWith : css selector of other sortables
|
||||
* - removable : boolean - whether to display delete button on items
|
||||
* - addItem : function(row,index,itemData) - when an item is added
|
||||
* - removeItem : function(itemData) - called when an item is removed
|
||||
* methods:
|
||||
* - addItem(itemData)
|
||||
* - removeItem(itemData)
|
||||
* - width(width)
|
||||
* - height(height)
|
||||
* - items()
|
||||
* - empty()
|
||||
*/
|
||||
$.widget( "nodered.editableList", {
|
||||
_create: function() {
|
||||
var that = this;
|
||||
|
||||
this.element.addClass('red-ui-editableList-list');
|
||||
this.uiWidth = this.element.width();
|
||||
this.uiContainer = this.element
|
||||
.wrap( "<div>" )
|
||||
.parent();
|
||||
this.topContainer = this.uiContainer.wrap("<div>").parent();
|
||||
|
||||
this.topContainer.addClass('red-ui-editableList');
|
||||
|
||||
if (this.options.addButton !== false) {
|
||||
var addLabel;
|
||||
if (typeof this.options.addButton === 'string') {
|
||||
addLabel = this.options.addButton
|
||||
} else {
|
||||
if (RED && RED._) {
|
||||
addLabel = RED._("editableList.add");
|
||||
} else {
|
||||
addLabel = 'add';
|
||||
}
|
||||
}
|
||||
$('<a href="#" class="editor-button editor-button-small" style="margin-top: 4px;"><i class="fa fa-plus"></i> '+addLabel+'</a>')
|
||||
.appendTo(this.topContainer)
|
||||
.click(function(evt) {
|
||||
evt.preventDefault();
|
||||
that.addItem({});
|
||||
});
|
||||
}
|
||||
|
||||
this.uiContainer.addClass("red-ui-editableList-container");
|
||||
|
||||
this.uiHeight = this.element.height();
|
||||
|
||||
var minHeight = this.element.css("minHeight");
|
||||
if (minHeight !== '0px') {
|
||||
this.uiContainer.css("minHeight",minHeight);
|
||||
this.element.css("minHeight",0);
|
||||
}
|
||||
if (this.options.height !== 'auto') {
|
||||
this.uiContainer.css("overflow-y","scroll");
|
||||
if (!isNaN(this.options.height)) {
|
||||
this.uiHeight = this.options.height;
|
||||
}
|
||||
}
|
||||
if (this.options.sortable) {
|
||||
var handle = (typeof this.options.sortable === 'string')?
|
||||
this.options.sortable :
|
||||
".red-ui-editableList-item-handle";
|
||||
var sortOptions = {
|
||||
axis: "y",
|
||||
update: function( event, ui ) {
|
||||
if (that.options.sortItems) {
|
||||
that.options.sortItems(that.items());
|
||||
}
|
||||
},
|
||||
handle:handle,
|
||||
cursor: "move",
|
||||
tolerance: "pointer",
|
||||
forcePlaceholderSize:true,
|
||||
placeholder: "red-ui-editabelList-item-placeholder",
|
||||
start: function(e, ui){
|
||||
ui.placeholder.height(ui.item.height()-4);
|
||||
}
|
||||
};
|
||||
if (this.options.connectWith) {
|
||||
sortOptions.connectWith = this.options.connectWith;
|
||||
}
|
||||
|
||||
this.element.sortable(sortOptions);
|
||||
}
|
||||
|
||||
this._resize();
|
||||
|
||||
// this.menu = this._createMenu(this.types, function(v) { that.type(v) });
|
||||
// this.type(this.options.default||this.types[0].value);
|
||||
},
|
||||
_resize: function() {
|
||||
var currentFullHeight = this.topContainer.height();
|
||||
var innerHeight = this.uiContainer.height();
|
||||
var delta = currentFullHeight - innerHeight;
|
||||
if (this.uiHeight !== 0) {
|
||||
this.uiContainer.height(this.uiHeight-delta);
|
||||
}
|
||||
if (this.options.resize) {
|
||||
this.options.resize();
|
||||
}
|
||||
if (this.options.resizeItem) {
|
||||
var that = this;
|
||||
this.element.children().each(function(i) {
|
||||
that.options.resizeItem($(this).find(".red-ui-editableList-item-content"),i);
|
||||
});
|
||||
}
|
||||
},
|
||||
_destroy: function() {
|
||||
},
|
||||
width: function(desiredWidth) {
|
||||
this.uiWidth = desiredWidth;
|
||||
this._resize();
|
||||
},
|
||||
height: function(desiredHeight) {
|
||||
this.uiHeight = desiredHeight;
|
||||
this._resize();
|
||||
},
|
||||
addItem: function(data) {
|
||||
var that = this;
|
||||
data = data || {};
|
||||
var li = $('<li>').appendTo(this.element);
|
||||
var row = $('<div/>').addClass("red-ui-editableList-item-content").appendTo(li);
|
||||
row.data('data',data);
|
||||
if (this.options.sortable === true) {
|
||||
$('<i class="red-ui-editableList-item-handle fa fa-bars"></i>').appendTo(li);
|
||||
li.addClass("red-ui-editableList-item-sortable");
|
||||
}
|
||||
if (this.options.removable) {
|
||||
var deleteButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove editor-button editor-button-small"}).appendTo(li);
|
||||
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
|
||||
li.addClass("red-ui-editableList-item-removable");
|
||||
deleteButton.click(function() {
|
||||
li.addClass("red-ui-editableList-item-deleting")
|
||||
li.fadeOut(300, function() {
|
||||
$(this).remove();
|
||||
if (that.options.removeItem) {
|
||||
that.options.removeItem(row.data('data'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
if (this.options.addItem) {
|
||||
var index = that.element.children().length-1;
|
||||
setTimeout(function() {
|
||||
that.options.addItem(row,index,data);
|
||||
},0);
|
||||
}
|
||||
},
|
||||
removeItem: function(data) {
|
||||
var items = this.element.children().filter(function(f) {
|
||||
return data === $(this).find(".red-ui-editableList-item-content").data('data');
|
||||
});
|
||||
items.remove();
|
||||
if (this.options.removeItem) {
|
||||
this.options.removeItem(data);
|
||||
}
|
||||
},
|
||||
items: function() {
|
||||
return this.element.children().map(function(i) { return $(this).find(".red-ui-editableList-item-content"); });
|
||||
},
|
||||
empty: function() {
|
||||
this.element.empty();
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
1333
editor/js/ui/editor.js
Normal file
150
editor/js/ui/keyboard.js
Normal file
@ -0,0 +1,150 @@
|
||||
/**
|
||||
* 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 handlers = {};
|
||||
|
||||
function resolveKeyEvent(evt) {
|
||||
var slot = handlers;
|
||||
if (evt.ctrlKey || evt.metaKey) {
|
||||
slot = slot.ctrl;
|
||||
}
|
||||
if (slot && evt.shiftKey) {
|
||||
slot = slot.shift;
|
||||
}
|
||||
if (slot && evt.altKey) {
|
||||
slot = slot.alt;
|
||||
}
|
||||
if (slot && slot[evt.keyCode]) {
|
||||
var handler = slot[evt.keyCode];
|
||||
if (handler.scope && handler.scope !== "*") {
|
||||
var target = evt.target;
|
||||
while (target.nodeName !== 'BODY' && target.id !== handler.scope) {
|
||||
target = target.parentElement;
|
||||
}
|
||||
if (target.nodeName === 'BODY') {
|
||||
handler = null;
|
||||
}
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
d3.select(window).on("keydown",function() {
|
||||
var handler = resolveKeyEvent(d3.event);
|
||||
if (handler && handler.ondown) {
|
||||
handler.ondown();
|
||||
}
|
||||
});
|
||||
d3.select(window).on("keyup",function() {
|
||||
var handler = resolveKeyEvent(d3.event);
|
||||
if (handler && handler.onup) {
|
||||
handler.onup();
|
||||
}
|
||||
});
|
||||
|
||||
function addHandler(scope,key,modifiers,ondown,onup) {
|
||||
var mod = modifiers;
|
||||
var cbdown = ondown;
|
||||
var cbup = onup;
|
||||
if (typeof modifiers == "function") {
|
||||
mod = {};
|
||||
cbdown = modifiers;
|
||||
cbup = ondown;
|
||||
}
|
||||
var slot = handlers;
|
||||
if (mod.ctrl) {
|
||||
slot.ctrl = slot.ctrl||{};
|
||||
slot = slot.ctrl;
|
||||
}
|
||||
if (mod.shift) {
|
||||
slot.shift = slot.shift||{};
|
||||
slot = slot.shift;
|
||||
}
|
||||
if (mod.alt) {
|
||||
slot.alt = slot.alt||{};
|
||||
slot = slot.alt;
|
||||
}
|
||||
slot[key] = {scope: scope, ondown:cbdown, onup:cbup};
|
||||
}
|
||||
|
||||
function removeHandler(key,modifiers) {
|
||||
var mod = modifiers || {};
|
||||
var slot = handlers;
|
||||
if (mod.ctrl) {
|
||||
slot = slot.ctrl;
|
||||
}
|
||||
if (slot && mod.shift) {
|
||||
slot = slot.shift;
|
||||
}
|
||||
if (slot && mod.alt) {
|
||||
slot = slot.alt;
|
||||
}
|
||||
if (slot) {
|
||||
delete slot[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/⌘</span> + <span class="help-key">a</span></td><td>'+RED._("keyboard.selectAll")+'</td></tr>'+
|
||||
'<tr><td><span class="help-key">Shift</span> + <span class="help-key">Click</span></td><td>'+RED._("keyboard.selectAllConnected")+'</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">Click</span></td><td>'+RED._("keyboard.addRemoveNode")+'</td></tr>'+
|
||||
'<tr><td><span class="help-key">Delete</span></td><td>'+RED._("keyboard.deleteSelected")+'</td></tr>'+
|
||||
'<tr><td> </td><td></td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">i</span></td><td>'+RED._("keyboard.importNode")+'</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">e</span></td><td>'+RED._("keyboard.exportNode")+'</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/⌘</span> + <span class="help-key">Space</span></td><td>'+RED._("keyboard.toggleSidebar")+'</td></tr>'+
|
||||
'<tr><td></td><td></td></tr>'+
|
||||
'<tr><td><span class="help-key">Delete</span></td><td>'+RED._("keyboard.deleteNode")+'</td></tr>'+
|
||||
'<tr><td></td><td></td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">c</span></td><td>'+RED._("keyboard.copyNode")+'</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">x</span></td><td>'+RED._("keyboard.cutNode")+'</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">v</span></td><td>'+RED._("keyboard.pasteNode")+'</td></tr>'+
|
||||
'</table>'+
|
||||
'</div>'+
|
||||
'</div>')
|
||||
.appendTo("body")
|
||||
.dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: "800",
|
||||
title:"Keyboard shortcuts",
|
||||
resizable: false
|
||||
});
|
||||
}
|
||||
|
||||
dialog.dialog("open");
|
||||
}
|
||||
|
||||
return {
|
||||
add: addHandler,
|
||||
remove: removeHandler,
|
||||
showHelp: showKeyboardHelp
|
||||
}
|
||||
|
||||
})();
|
492
editor/js/ui/library.js
Normal file
@ -0,0 +1,492 @@
|
||||
/**
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
RED.library = (function() {
|
||||
|
||||
|
||||
var exportToLibraryDialog;
|
||||
|
||||
function loadFlowLibrary() {
|
||||
$.getJSON("library/flows",function(data) {
|
||||
//console.log(data);
|
||||
|
||||
var buildMenu = function(data,root) {
|
||||
var i;
|
||||
var li;
|
||||
var a;
|
||||
var ul = document.createElement("ul");
|
||||
if (root === "") {
|
||||
ul.id = "menu-item-import-library-submenu";
|
||||
}
|
||||
ul.className = "dropdown-menu";
|
||||
if (data.d) {
|
||||
for (i in data.d) {
|
||||
if (data.d.hasOwnProperty(i)) {
|
||||
li = document.createElement("li");
|
||||
li.className = "dropdown-submenu pull-left";
|
||||
a = document.createElement("a");
|
||||
a.href="#";
|
||||
var label = i.replace(/^node-red-contrib-/,"").replace(/^node-red-node-/,"").replace(/-/," ").replace(/_/," ");
|
||||
a.innerHTML = label;
|
||||
li.appendChild(a);
|
||||
li.appendChild(buildMenu(data.d[i],root+(root!==""?"/":"")+i));
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data.f) {
|
||||
for (i in data.f) {
|
||||
if (data.f.hasOwnProperty(i)) {
|
||||
li = document.createElement("li");
|
||||
a = document.createElement("a");
|
||||
a.href="#";
|
||||
a.innerHTML = data.f[i];
|
||||
a.flowName = root+(root!==""?"/":"")+data.f[i];
|
||||
a.onclick = function() {
|
||||
$.get('library/flows/'+this.flowName, function(data) {
|
||||
RED.view.importNodes(data);
|
||||
});
|
||||
};
|
||||
li.appendChild(a);
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ul;
|
||||
};
|
||||
var examples;
|
||||
if (data.d && data.d._examples_) {
|
||||
examples = data.d._examples_;
|
||||
delete data.d._examples_;
|
||||
}
|
||||
var menu = buildMenu(data,"");
|
||||
$("#menu-item-import-examples").remove();
|
||||
if (examples) {
|
||||
RED.menu.addItem("menu-item-import",{id:"menu-item-import-examples",label:RED._("menu.label.examples"),options:[]})
|
||||
$("#menu-item-import-examples-submenu").replaceWith(buildMenu(examples,"_examples_"));
|
||||
}
|
||||
//TODO: need an api in RED.menu for this
|
||||
$("#menu-item-import-library-submenu").replaceWith(menu);
|
||||
});
|
||||
}
|
||||
|
||||
function createUI(options) {
|
||||
var libraryData = {};
|
||||
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"); };
|
||||
li.onmouseout = function(e) { $(this).removeClass("list-hover"); };
|
||||
return li;
|
||||
}
|
||||
|
||||
function buildFileList(root,data) {
|
||||
var ul = document.createElement("ul");
|
||||
var li;
|
||||
for (var i=0;i<data.length;i++) {
|
||||
var v = data[i];
|
||||
if (typeof v === "string") {
|
||||
// directory
|
||||
li = buildFileListItem(v);
|
||||
li.onclick = (function () {
|
||||
var dirName = v;
|
||||
return function(e) {
|
||||
var bcli = $('<li class="active"><span class="divider">/</span> <a href="#">'+dirName+'</a></li>');
|
||||
$("a",bcli).click(function(e) {
|
||||
$(this).parent().nextAll().remove();
|
||||
$.getJSON("library/"+options.url+root+dirName,function(data) {
|
||||
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
|
||||
});
|
||||
e.stopPropagation();
|
||||
});
|
||||
var bc = $("#node-dialog-library-breadcrumbs");
|
||||
$(".active",bc).removeClass("active");
|
||||
bc.append(bcli);
|
||||
$.getJSON("library/"+options.url+root+dirName,function(data) {
|
||||
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
|
||||
});
|
||||
}
|
||||
})();
|
||||
li.innerHTML = '<i class="fa fa-folder"></i> '+v+"</i>";
|
||||
ul.appendChild(li);
|
||||
} else {
|
||||
// file
|
||||
li = buildFileListItem(v);
|
||||
li.innerHTML = v.name;
|
||||
li.onclick = (function() {
|
||||
var item = v;
|
||||
return function(e) {
|
||||
$(".list-selected",ul).removeClass("list-selected");
|
||||
$(this).addClass("list-selected");
|
||||
$.get("library/"+options.url+root+item.fn, function(data) {
|
||||
selectedLibraryItem = item;
|
||||
libraryEditor.setValue(data,-1);
|
||||
});
|
||||
}
|
||||
})();
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
return ul;
|
||||
}
|
||||
|
||||
$('#node-input-name').css("width","60%").after(
|
||||
'<div class="btn-group" style="margin-left: 5px;">'+
|
||||
'<a id="node-input-'+options.type+'-lookup" class="editor-button" data-toggle="dropdown"><i class="fa fa-book"></i> <i class="fa fa-caret-down"></i></a>'+
|
||||
'<ul class="dropdown-menu pull-right" role="menu">'+
|
||||
'<li><a id="node-input-'+options.type+'-menu-open-library" tabindex="-1" href="#">'+RED._("library.openLibrary")+'</a></li>'+
|
||||
'<li><a id="node-input-'+options.type+'-menu-save-library" tabindex="-1" href="#">'+RED._("library.saveToLibrary")+'</a></li>'+
|
||||
'</ul></div>'
|
||||
);
|
||||
|
||||
|
||||
|
||||
$('#node-input-'+options.type+'-menu-open-library').click(function(e) {
|
||||
$("#node-select-library").children().remove();
|
||||
var bc = $("#node-dialog-library-breadcrumbs");
|
||||
bc.children().first().nextAll().remove();
|
||||
libraryEditor.setValue('',-1);
|
||||
|
||||
$.getJSON("library/"+options.url,function(data) {
|
||||
$("#node-select-library").append(buildFileList("/",data));
|
||||
$("#node-dialog-library-breadcrumbs a").click(function(e) {
|
||||
$(this).parent().nextAll().remove();
|
||||
$("#node-select-library").children().first().replaceWith(buildFileList("/",data));
|
||||
e.stopPropagation();
|
||||
});
|
||||
$( "#node-dialog-library-lookup" ).dialog( "open" );
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
$('#node-input-'+options.type+'-menu-save-library').click(function(e) {
|
||||
//var found = false;
|
||||
var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,"");
|
||||
|
||||
//var buildPathList = function(data,root) {
|
||||
// var paths = [];
|
||||
// if (data.d) {
|
||||
// for (var i in data.d) {
|
||||
// var dn = root+(root==""?"":"/")+i;
|
||||
// var d = {
|
||||
// label:dn,
|
||||
// files:[]
|
||||
// };
|
||||
// for (var f in data.d[i].f) {
|
||||
// d.files.push(data.d[i].f[f].fn.split("/").slice(-1)[0]);
|
||||
// }
|
||||
// paths.push(d);
|
||||
// paths = paths.concat(buildPathList(data.d[i],root+(root==""?"":"/")+i));
|
||||
// }
|
||||
// }
|
||||
// return paths;
|
||||
//};
|
||||
$("#node-dialog-library-save-folder").attr("value","");
|
||||
|
||||
var filename = name.replace(/[^\w-]/g,"-");
|
||||
if (filename === "") {
|
||||
filename = "unnamed-"+options.type;
|
||||
}
|
||||
$("#node-dialog-library-save-filename").attr("value",filename+".js");
|
||||
|
||||
//var paths = buildPathList(libraryData,"");
|
||||
//$("#node-dialog-library-save-folder").autocomplete({
|
||||
// minLength: 0,
|
||||
// source: paths,
|
||||
// select: function( event, ui ) {
|
||||
// $("#node-dialog-library-save-filename").autocomplete({
|
||||
// minLength: 0,
|
||||
// source: ui.item.files
|
||||
// });
|
||||
// }
|
||||
//});
|
||||
|
||||
$( "#node-dialog-library-save" ).dialog( "open" );
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
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: RED._("library.typeLibrary", {type:options.type}),
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 800,
|
||||
height: 450,
|
||||
buttons: [
|
||||
{
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: RED._("common.label.load"),
|
||||
class: "primary",
|
||||
click: function() {
|
||||
if (selectedLibraryItem) {
|
||||
for (var i=0;i<options.fields.length;i++) {
|
||||
var field = options.fields[i];
|
||||
$("#node-input-"+field).val(selectedLibraryItem[field]);
|
||||
}
|
||||
options.editor.setValue(libraryEditor.getValue(),-1);
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
var form = $("form",this);
|
||||
form.height(form.parent().height()-30);
|
||||
$("#node-select-library-text").height("100%");
|
||||
$(".form-row:last-child",form).children().height(form.height()-60);
|
||||
},
|
||||
resize: function(e) {
|
||||
var form = $("form",this);
|
||||
form.height(form.parent().height()-30);
|
||||
$(".form-row:last-child",form).children().height(form.height()-60);
|
||||
}
|
||||
});
|
||||
|
||||
function saveToLibrary(overwrite) {
|
||||
var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,"");
|
||||
if (name === "") {
|
||||
name = RED._("library.unnamedType",{type:options.type});
|
||||
}
|
||||
var filename = $("#node-dialog-library-save-filename").val().replace(/(^\s*)|(\s*$)/g,"");
|
||||
var pathname = $("#node-dialog-library-save-folder").val().replace(/(^\s*)|(\s*$)/g,"");
|
||||
if (filename === "" || !/.+\.js$/.test(filename)) {
|
||||
RED.notify(RED._("library.invalidFilename"),"warning");
|
||||
return;
|
||||
}
|
||||
var fullpath = pathname+(pathname===""?"":"/")+filename;
|
||||
if (!overwrite) {
|
||||
//var pathnameParts = pathname.split("/");
|
||||
//var exists = false;
|
||||
//var ds = libraryData;
|
||||
//for (var pnp in pathnameParts) {
|
||||
// if (ds.d && pathnameParts[pnp] in ds.d) {
|
||||
// ds = ds.d[pathnameParts[pnp]];
|
||||
// } else {
|
||||
// ds = null;
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
//if (ds && ds.f) {
|
||||
// for (var f in ds.f) {
|
||||
// if (ds.f[f].fn == fullpath) {
|
||||
// exists = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//if (exists) {
|
||||
// $("#node-dialog-library-save-content").html(RED._("library.dialogSaveOverwrite",{libraryType:options.type,libraryName:fullpath}));
|
||||
// $("#node-dialog-library-save-confirm").dialog( "open" );
|
||||
// return;
|
||||
//}
|
||||
}
|
||||
var queryArgs = [];
|
||||
var data = {};
|
||||
for (var i=0;i<options.fields.length;i++) {
|
||||
var field = options.fields[i];
|
||||
if (field == "name") {
|
||||
data.name = name;
|
||||
} else {
|
||||
data[field] = $("#node-input-"+field).val();
|
||||
}
|
||||
}
|
||||
|
||||
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(RED._("library.savedType", {type:options.type}),"success");
|
||||
}).fail(function(xhr,textStatus,err) {
|
||||
if (xhr.status === 401) {
|
||||
RED.notify(RED._("library.saveFailed",{message:RED._("user.notAuthorized")}),"error");
|
||||
} else {
|
||||
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
|
||||
}
|
||||
});
|
||||
}
|
||||
$( "#node-dialog-library-save-confirm" ).dialog({
|
||||
title: RED._("library.saveToLibrary"),
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 530,
|
||||
height: 230,
|
||||
buttons: [
|
||||
{
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: RED._("common.label.save"),
|
||||
class: "primary",
|
||||
click: function() {
|
||||
saveToLibrary(true);
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
$( "#node-dialog-library-save" ).dialog({
|
||||
title: RED._("library.saveToLibrary"),
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 530,
|
||||
height: 230,
|
||||
buttons: [
|
||||
{
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: RED._("common.label.save"),
|
||||
class: "primary",
|
||||
click: function() {
|
||||
saveToLibrary(false);
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function exportFlow() {
|
||||
//TODO: don't rely on the main dialog
|
||||
var nns = RED.nodes.createExportableNodeSet(RED.view.selection().nodes);
|
||||
$("#node-input-library-filename").attr('nodes',JSON.stringify(nns));
|
||||
exportToLibraryDialog.dialog( "open" );
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
RED.events.on("view: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();
|
||||
}
|
||||
|
||||
exportToLibraryDialog = $('<div id="library-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
|
||||
.appendTo("body")
|
||||
.dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
resizable: false,
|
||||
title: RED._("library.exportToLibrary"),
|
||||
buttons: [
|
||||
{
|
||||
id: "library-dialog-cancel",
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "library-dialog-ok",
|
||||
class: "primary",
|
||||
text: RED._("common.label.export"),
|
||||
click: function() {
|
||||
//TODO: move this to RED.library
|
||||
var flowName = $("#node-input-library-filename").val();
|
||||
if (!/^\s*$/.test(flowName)) {
|
||||
$.ajax({
|
||||
url:'library/flows/'+flowName,
|
||||
type: "POST",
|
||||
data: $("#node-input-library-filename").attr('nodes'),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function() {
|
||||
RED.library.loadFlowLibrary();
|
||||
RED.notify(RED._("library.savedNodes"),"success");
|
||||
}).fail(function(xhr,textStatus,err) {
|
||||
if (xhr.status === 401) {
|
||||
RED.notify(RED._("library.saveFailed",{message:RED._("user.notAuthorized")}),"error");
|
||||
} else {
|
||||
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
|
||||
}
|
||||
});
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
||||
},
|
||||
close: function(e) {
|
||||
}
|
||||
});
|
||||
exportToLibraryDialog.children(".dialog-form").append($(
|
||||
'<div class="form-row">'+
|
||||
'<label for="node-input-library-filename" data-i18n="[append]editor:library.filename"><i class="fa fa-file"></i> </label>'+
|
||||
'<input type="text" id="node-input-library-filename" data-i18n="[placeholder]editor:library.fullFilenamePlaceholder">'+
|
||||
'<input type="text" style="display: none;" />'+ // Second hidden input to prevent submit on Enter
|
||||
'</div>'
|
||||
));
|
||||
},
|
||||
create: createUI,
|
||||
loadFlowLibrary: loadFlowLibrary,
|
||||
|
||||
export: exportFlow
|
||||
}
|
||||
})();
|
257
editor/js/ui/menu.js
Normal file
@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Copyright 2014 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.menu = (function() {
|
||||
|
||||
var menuItems = {};
|
||||
|
||||
function createMenuItem(opt) {
|
||||
var item;
|
||||
|
||||
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");
|
||||
opt.onselect.call(opt, true);
|
||||
} else if (savedStateActive === false) {
|
||||
link.removeClass("active");
|
||||
opt.onselect.call(opt, false);
|
||||
} else if (opt.hasOwnProperty("selected")) {
|
||||
if (opt.selected) {
|
||||
link.addClass("active");
|
||||
} else {
|
||||
link.removeClass("active");
|
||||
}
|
||||
opt.onselect.call(opt, opt.selected);
|
||||
}
|
||||
}
|
||||
|
||||
if (opt === null) {
|
||||
item = $('<li class="divider"></li>');
|
||||
} else {
|
||||
item = $('<li></li>');
|
||||
|
||||
if (opt.group) {
|
||||
item.addClass("menu-group-"+opt.group);
|
||||
|
||||
}
|
||||
var linkContent = '<a '+(opt.id?'id="'+opt.id+'" ':'')+'tabindex="-1" href="#">';
|
||||
if (opt.toggle) {
|
||||
linkContent += '<i class="fa fa-square pull-left"></i>';
|
||||
linkContent += '<i class="fa fa-check-square pull-left"></i>';
|
||||
|
||||
}
|
||||
if (opt.icon !== undefined) {
|
||||
if (/\.png/.test(opt.icon)) {
|
||||
linkContent += '<img src="'+opt.icon+'"/> ';
|
||||
} else {
|
||||
linkContent += '<i class="'+(opt.icon?opt.icon:'" style="display: inline-block;"')+'"></i> ';
|
||||
}
|
||||
}
|
||||
|
||||
if (opt.sublabel) {
|
||||
linkContent += '<span class="menu-label-container"><span class="menu-label">'+opt.label+'</span>'+
|
||||
'<span class="menu-sublabel">'+opt.sublabel+'</span></span>'
|
||||
} else {
|
||||
linkContent += '<span class="menu-label">'+opt.label+'</span>'
|
||||
}
|
||||
|
||||
linkContent += '</a>';
|
||||
|
||||
var link = $(linkContent).appendTo(item);
|
||||
|
||||
menuItems[opt.id] = opt;
|
||||
|
||||
if (opt.onselect) {
|
||||
link.click(function() {
|
||||
if ($(this).parent().hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
if (opt.toggle) {
|
||||
var selected = isSelected(opt.id);
|
||||
if (typeof opt.toggle === "string") {
|
||||
if (!selected) {
|
||||
for (var m in menuItems) {
|
||||
if (menuItems.hasOwnProperty(m)) {
|
||||
var mi = menuItems[m];
|
||||
if (mi.id != opt.id && opt.toggle == mi.toggle) {
|
||||
setSelected(mi.id,false);
|
||||
}
|
||||
}
|
||||
}
|
||||
setSelected(opt.id,true);
|
||||
}
|
||||
} else {
|
||||
setSelected(opt.id, !selected);
|
||||
}
|
||||
} else {
|
||||
opt.onselect.call(opt);
|
||||
}
|
||||
});
|
||||
setInitialState();
|
||||
} else if (opt.href) {
|
||||
link.attr("target","_blank").attr("href",opt.href);
|
||||
} else if (!opt.options) {
|
||||
item.addClass("disabled");
|
||||
link.click(function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
if (opt.options) {
|
||||
item.addClass("dropdown-submenu pull-left");
|
||||
var submenu = $('<ul id="'+opt.id+'-submenu" class="dropdown-menu"></ul>').appendTo(item);
|
||||
|
||||
for (var i=0;i<opt.options.length;i++) {
|
||||
var li = createMenuItem(opt.options[i]);
|
||||
if (li) {
|
||||
li.appendTo(submenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (opt.disabled) {
|
||||
item.addClass("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return item;
|
||||
|
||||
}
|
||||
function createMenu(options) {
|
||||
|
||||
var button = $("#"+options.id);
|
||||
|
||||
//button.click(function(event) {
|
||||
// $("#"+options.id+"-submenu").show();
|
||||
// event.preventDefault();
|
||||
//});
|
||||
|
||||
|
||||
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];
|
||||
if (opt !== null || !lastAddedSeparator) {
|
||||
var li = createMenuItem(opt);
|
||||
if (li) {
|
||||
li.appendTo(topMenu);
|
||||
lastAddedSeparator = (opt === null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isSavedStateActive(id) {
|
||||
return RED.settings.get("menu-" + id);
|
||||
}
|
||||
|
||||
function isSelected(id) {
|
||||
return $("#" + id).hasClass("active");
|
||||
}
|
||||
|
||||
function setSavedState(id, state) {
|
||||
RED.settings.set("menu-" + id, state);
|
||||
}
|
||||
|
||||
function setSelected(id,state) {
|
||||
if (isSelected(id) == state) {
|
||||
return;
|
||||
}
|
||||
var opt = menuItems[id];
|
||||
if (state) {
|
||||
$("#"+id).addClass("active");
|
||||
} else {
|
||||
$("#"+id).removeClass("active");
|
||||
}
|
||||
if (opt && opt.onselect) {
|
||||
opt.onselect.call(opt,state);
|
||||
}
|
||||
setSavedState(id, state);
|
||||
}
|
||||
|
||||
function setDisabled(id,state) {
|
||||
if (state) {
|
||||
$("#"+id).parent().addClass("disabled");
|
||||
} else {
|
||||
$("#"+id).parent().removeClass("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
function addItem(id,opt) {
|
||||
var item = createMenuItem(opt);
|
||||
if (opt.group) {
|
||||
var groupItems = $("#"+id+"-submenu").children(".menu-group-"+opt.group);
|
||||
if (groupItems.length === 0) {
|
||||
item.appendTo("#"+id+"-submenu");
|
||||
} else {
|
||||
for (var i=0;i<groupItems.length;i++) {
|
||||
var groupItem = groupItems[i];
|
||||
var label = $(groupItem).find(".menu-label").html();
|
||||
if (opt.label < label) {
|
||||
$(groupItem).before(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i === groupItems.length) {
|
||||
item.appendTo("#"+id+"-submenu");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
item.appendTo("#"+id+"-submenu");
|
||||
}
|
||||
}
|
||||
function removeItem(id) {
|
||||
$("#"+id).parent().remove();
|
||||
}
|
||||
|
||||
function setAction(id,action) {
|
||||
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 {
|
||||
init: createMenu,
|
||||
setSelected: setSelected,
|
||||
isSelected: isSelected,
|
||||
setDisabled: setDisabled,
|
||||
addItem: addItem,
|
||||
removeItem: removeItem,
|
||||
setAction: setAction
|
||||
//TODO: add an api for replacing a submenu - see library.js:loadFlowLibrary
|
||||
}
|
||||
})();
|
73
editor/js/ui/notifications.js
Normal file
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
RED.notify = (function() {
|
||||
var currentNotifications = [];
|
||||
var c = 0;
|
||||
return function(msg,type,fixed,timeout) {
|
||||
if (currentNotifications.length > 4) {
|
||||
var ll = currentNotifications.length;
|
||||
for (var i = 0;ll > 4 && i<currentNotifications.length;i+=1) {
|
||||
var notifiction = currentNotifications[i];
|
||||
if (!notifiction.fixed) {
|
||||
window.clearTimeout(notifiction.timeoutid);
|
||||
notifiction.close();
|
||||
ll -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
var n = document.createElement("div");
|
||||
n.id="red-notification-"+c;
|
||||
n.className = "notification";
|
||||
n.fixed = fixed;
|
||||
if (type) {
|
||||
n.className = "notification notification-"+type;
|
||||
}
|
||||
n.style.display = "none";
|
||||
n.innerHTML = msg;
|
||||
$("#notifications").append(n);
|
||||
$(n).slideDown(300);
|
||||
n.close = (function() {
|
||||
var nn = n;
|
||||
return function() {
|
||||
currentNotifications.splice(currentNotifications.indexOf(nn),1);
|
||||
$(nn).slideUp(300, function() {
|
||||
nn.parentNode.removeChild(nn);
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
n.update = (function() {
|
||||
var nn = n;
|
||||
return function(msg) {
|
||||
nn.innerHTML = msg;
|
||||
}
|
||||
})();
|
||||
|
||||
if (!fixed) {
|
||||
$(n).click((function() {
|
||||
var nn = n;
|
||||
return function() {
|
||||
nn.close();
|
||||
window.clearTimeout(nn.timeoutid);
|
||||
};
|
||||
})());
|
||||
n.timeoutid = window.setTimeout(n.close,timeout||3000);
|
||||
}
|
||||
currentNotifications.push(n);
|
||||
c+=1;
|
||||
return n;
|
||||
}
|
||||
})();
|
451
editor/js/ui/palette.js
Normal file
@ -0,0 +1,451 @@
|
||||
/**
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
RED.palette = (function() {
|
||||
|
||||
var exclusion = ['config','unknown','deprecated'];
|
||||
var core = ['subflows', 'input', 'output', 'function', 'social', 'mobile', 'storage', 'analysis', 'advanced'];
|
||||
|
||||
var categoryContainers = {};
|
||||
|
||||
function createCategoryContainer(category, label){
|
||||
label = label || category.replace("_", " ");
|
||||
var catDiv = $('<div id="palette-container-'+category+'" class="palette-category palette-close hide">'+
|
||||
'<div id="palette-header-'+category+'" class="palette-header"><i class="expanded fa fa-angle-down"></i><span>'+label+'</span></div>'+
|
||||
'<div class="palette-content" id="palette-base-category-'+category+'">'+
|
||||
'<div id="palette-'+category+'-input"></div>'+
|
||||
'<div id="palette-'+category+'-output"></div>'+
|
||||
'<div id="palette-'+category+'-function"></div>'+
|
||||
'</div>'+
|
||||
'</div>').appendTo("#palette-container");
|
||||
|
||||
categoryContainers[category] = {
|
||||
container: catDiv,
|
||||
close: function() {
|
||||
catDiv.removeClass("palette-open");
|
||||
catDiv.addClass("palette-closed");
|
||||
$("#palette-base-category-"+category).slideUp();
|
||||
$("#palette-header-"+category+" i").removeClass("expanded");
|
||||
},
|
||||
open: function() {
|
||||
catDiv.addClass("palette-open");
|
||||
catDiv.removeClass("palette-closed");
|
||||
$("#palette-base-category-"+category).slideDown();
|
||||
$("#palette-header-"+category+" i").addClass("expanded");
|
||||
},
|
||||
toggle: function() {
|
||||
if (catDiv.hasClass("palette-open")) {
|
||||
categoryContainers[category].close();
|
||||
} else {
|
||||
categoryContainers[category].open();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$("#palette-header-"+category).on('click', function(e) {
|
||||
categoryContainers[category].toggle();
|
||||
});
|
||||
}
|
||||
|
||||
function setLabel(type, el,label, info) {
|
||||
var nodeWidth = 82;
|
||||
var nodeHeight = 25;
|
||||
var lineHeight = 20;
|
||||
var portHeight = 10;
|
||||
|
||||
var words = label.split(/[ -]/);
|
||||
|
||||
var displayLines = [];
|
||||
|
||||
var currentLine = words[0];
|
||||
var currentLineWidth = RED.view.calculateTextWidth(currentLine, "palette_label", 0);
|
||||
|
||||
for (var i=1;i<words.length;i++) {
|
||||
var newWidth = RED.view.calculateTextWidth(currentLine+" "+words[i], "palette_label", 0);
|
||||
if (newWidth < nodeWidth) {
|
||||
currentLine += " "+words[i];
|
||||
currentLineWidth = newWidth;
|
||||
} else {
|
||||
displayLines.push(currentLine);
|
||||
currentLine = words[i];
|
||||
currentLineWidth = RED.view.calculateTextWidth(currentLine, "palette_label", 0);
|
||||
}
|
||||
}
|
||||
displayLines.push(currentLine);
|
||||
|
||||
var lines = displayLines.join("<br/>");
|
||||
var multiLineNodeHeight = 8+(lineHeight*displayLines.length);
|
||||
el.css({height:multiLineNodeHeight+"px"});
|
||||
|
||||
var labelElement = el.find(".palette_label");
|
||||
labelElement.html(lines);
|
||||
|
||||
el.find(".palette_port").css({top:(multiLineNodeHeight/2-5)+"px"});
|
||||
|
||||
var popOverContent;
|
||||
try {
|
||||
var l = "<p><b>"+label+"</b></p>";
|
||||
if (label != type) {
|
||||
l = "<p><b>"+label+"</b><br/><i>"+type+"</i></p>";
|
||||
}
|
||||
popOverContent = $(l+(info?info:$("script[data-help-name|='"+type+"']").html()||"<p>"+RED._("palette.noInfo")+"</p>").trim())
|
||||
.filter(function(n) {
|
||||
return (this.nodeType == 1 && this.nodeName == "P") || (this.nodeType == 3 && this.textContent.trim().length > 0)
|
||||
}).slice(0,2);
|
||||
} catch(err) {
|
||||
// Malformed HTML may cause errors. TODO: need to understand what can break
|
||||
// NON-NLS: internal debug
|
||||
console.log("Error generating pop-over label for ",type);
|
||||
console.log(err.toString());
|
||||
popOverContent = "<p><b>"+label+"</b></p><p>"+RED._("palette.noInfo")+"</p>";
|
||||
}
|
||||
|
||||
el.data('popover').setContent(popOverContent);
|
||||
}
|
||||
|
||||
function escapeNodeType(nt) {
|
||||
return nt.replace(" ","_").replace(".","_").replace(":","_");
|
||||
}
|
||||
|
||||
function addNodeType(nt,def) {
|
||||
var nodeTypeId = escapeNodeType(nt);
|
||||
if ($("#palette_node_"+nodeTypeId).length) {
|
||||
return;
|
||||
}
|
||||
if (exclusion.indexOf(def.category)===-1) {
|
||||
|
||||
var category = def.category.replace(" ","_");
|
||||
var rootCategory = category.split("-")[0];
|
||||
|
||||
var d = document.createElement("div");
|
||||
d.id = "palette_node_"+nodeTypeId;
|
||||
d.type = nt;
|
||||
|
||||
var label = /^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1];
|
||||
if (typeof def.paletteLabel !== "undefined") {
|
||||
try {
|
||||
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+nt+".paletteLabel",err);
|
||||
}
|
||||
}
|
||||
|
||||
$('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d);
|
||||
|
||||
d.className="palette_node";
|
||||
|
||||
|
||||
if (def.icon) {
|
||||
var icon_url = "arrow-in.png";
|
||||
try {
|
||||
icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon);
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+nt+".icon",err);
|
||||
}
|
||||
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;
|
||||
|
||||
if (def.outputs > 0) {
|
||||
var portOut = document.createElement("div");
|
||||
portOut.className = "palette_port palette_port_output";
|
||||
d.appendChild(portOut);
|
||||
}
|
||||
|
||||
if (def.inputs > 0) {
|
||||
var portIn = document.createElement("div");
|
||||
portIn.className = "palette_port palette_port_input";
|
||||
d.appendChild(portIn);
|
||||
}
|
||||
|
||||
if ($("#palette-base-category-"+rootCategory).length === 0) {
|
||||
if(core.indexOf(rootCategory) !== -1){
|
||||
createCategoryContainer(rootCategory, RED._("node-red:palette.label."+rootCategory, {defaultValue:rootCategory}));
|
||||
} else {
|
||||
var ns = def.set.id;
|
||||
createCategoryContainer(rootCategory, RED._(ns+":palette.label."+rootCategory, {defaultValue:rootCategory}));
|
||||
}
|
||||
}
|
||||
$("#palette-container-"+rootCategory).show();
|
||||
|
||||
if ($("#palette-"+category).length === 0) {
|
||||
$("#palette-base-category-"+rootCategory).append('<div id="palette-'+category+'"></div>');
|
||||
}
|
||||
|
||||
$("#palette-"+category).append(d);
|
||||
d.onmousedown = function(e) { e.preventDefault(); };
|
||||
|
||||
RED.popover.create({
|
||||
target:$(d),
|
||||
content: "hi",
|
||||
delay: { show: 750, hide: 50 }
|
||||
});
|
||||
|
||||
// $(d).popover({
|
||||
// title:d.type,
|
||||
// placement:"right",
|
||||
// trigger: "hover",
|
||||
// delay: { show: 750, hide: 50 },
|
||||
// html: true,
|
||||
// container:'body'
|
||||
// });
|
||||
$(d).click(function() {
|
||||
RED.view.focus();
|
||||
var helpText;
|
||||
if (nt.indexOf("subflow:") === 0) {
|
||||
helpText = marked(RED.nodes.subflow(nt.substring(8)).info||"");
|
||||
} else {
|
||||
helpText = $("script[data-help-name|='"+d.type+"']").html()||"";
|
||||
}
|
||||
var help = '<div class="node-help">'+helpText+"</div>";
|
||||
RED.sidebar.info.set(help);
|
||||
});
|
||||
var chart = $("#chart");
|
||||
var chartOffset = chart.offset();
|
||||
var chartSVG = $("#chart>svg").get(0);
|
||||
var activeSpliceLink;
|
||||
var mouseX;
|
||||
var mouseY;
|
||||
var spliceTimer;
|
||||
$(d).draggable({
|
||||
helper: 'clone',
|
||||
appendTo: 'body',
|
||||
revert: true,
|
||||
revertDuration: 50,
|
||||
start: function() {RED.view.focus();},
|
||||
stop: function() { d3.select('.link_splice').classed('link_splice',false); if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null;}},
|
||||
drag: function(e,ui) {
|
||||
// TODO: this is the margin-left of palette node. Hard coding
|
||||
// it here makes me sad
|
||||
//console.log(ui.helper.position());
|
||||
ui.position.left += 17.5;
|
||||
if (def.inputs > 0 && def.outputs > 0) {
|
||||
mouseX = ui.position.left+(ui.helper.width()/2) - chartOffset.left + chart.scrollLeft();
|
||||
mouseY = ui.position.top+(ui.helper.height()/2) - chartOffset.top + chart.scrollTop();
|
||||
|
||||
|
||||
if (!spliceTimer) {
|
||||
spliceTimer = setTimeout(function() {
|
||||
var nodes = [];
|
||||
var bestDistance = Infinity;
|
||||
var bestLink = null;
|
||||
if (chartSVG.getIntersectionList) {
|
||||
var svgRect = chartSVG.createSVGRect();
|
||||
svgRect.x = mouseX;
|
||||
svgRect.y = mouseY;
|
||||
svgRect.width = 1;
|
||||
svgRect.height = 1;
|
||||
nodes = chartSVG.getIntersectionList(svgRect,chartSVG);
|
||||
mouseX /= RED.view.scale();
|
||||
mouseY /= RED.view.scale();
|
||||
} else {
|
||||
// Firefox doesn't do getIntersectionList and that
|
||||
// makes us sad
|
||||
mouseX /= RED.view.scale();
|
||||
mouseY /= RED.view.scale();
|
||||
nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
|
||||
}
|
||||
for (var i=0;i<nodes.length;i++) {
|
||||
if (d3.select(nodes[i]).classed('link_background')) {
|
||||
var length = nodes[i].getTotalLength();
|
||||
for (var j=0;j<length;j+=10) {
|
||||
var p = nodes[i].getPointAtLength(j);
|
||||
var d2 = ((p.x-mouseX)*(p.x-mouseX))+((p.y-mouseY)*(p.y-mouseY));
|
||||
if (d2 < 200 && d2 < bestDistance) {
|
||||
bestDistance = d2;
|
||||
bestLink = nodes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (activeSpliceLink && activeSpliceLink !== bestLink) {
|
||||
d3.select(activeSpliceLink.parentNode).classed('link_splice',false);
|
||||
}
|
||||
if (bestLink) {
|
||||
d3.select(bestLink.parentNode).classed('link_splice',true)
|
||||
} else {
|
||||
d3.select('.link_splice').classed('link_splice',false);
|
||||
}
|
||||
if (activeSpliceLink !== bestLink) {
|
||||
if (bestLink) {
|
||||
$(ui.helper).data('splice',d3.select(bestLink).data()[0]);
|
||||
} else {
|
||||
$(ui.helper).removeData('splice');
|
||||
}
|
||||
}
|
||||
activeSpliceLink = bestLink;
|
||||
spliceTimer = null;
|
||||
},200);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var nodeInfo = null;
|
||||
if (def.category == "subflows") {
|
||||
$(d).dblclick(function(e) {
|
||||
RED.workspaces.show(nt.substring(8));
|
||||
e.preventDefault();
|
||||
});
|
||||
nodeInfo = marked(def.info||"");
|
||||
}
|
||||
setLabel(nt,$(d),label,nodeInfo);
|
||||
|
||||
var categoryNode = $("#palette-container-"+category);
|
||||
if (categoryNode.find(".palette_node").length === 1) {
|
||||
categoryContainers[category].open();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function removeNodeType(nt) {
|
||||
var nodeTypeId = escapeNodeType(nt);
|
||||
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);
|
||||
$("#palette_node_"+nodeTypeId).hide();
|
||||
}
|
||||
|
||||
function showNodeType(nt) {
|
||||
var nodeTypeId = escapeNodeType(nt);
|
||||
$("#palette_node_"+nodeTypeId).show();
|
||||
}
|
||||
|
||||
function refreshNodeTypes() {
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
var paletteNode = $("#palette_node_subflow_"+sf.id.replace(".","_"));
|
||||
var portInput = paletteNode.find(".palette_port_input");
|
||||
var portOutput = paletteNode.find(".palette_port_output");
|
||||
|
||||
if (portInput.length === 0 && sf.in.length > 0) {
|
||||
var portIn = document.createElement("div");
|
||||
portIn.className = "palette_port palette_port_input";
|
||||
paletteNode.append(portIn);
|
||||
} else if (portInput.length !== 0 && sf.in.length === 0) {
|
||||
portInput.remove();
|
||||
}
|
||||
|
||||
if (portOutput.length === 0 && sf.out.length > 0) {
|
||||
var portOut = document.createElement("div");
|
||||
portOut.className = "palette_port palette_port_output";
|
||||
paletteNode.append(portOut);
|
||||
} else if (portOutput.length !== 0 && sf.out.length === 0) {
|
||||
portOutput.remove();
|
||||
}
|
||||
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||""));
|
||||
});
|
||||
}
|
||||
|
||||
function filterChange() {
|
||||
var val = $("#palette-search-input").val();
|
||||
if (val === "") {
|
||||
$("#palette-search-clear").hide();
|
||||
} else {
|
||||
$("#palette-search-clear").show();
|
||||
}
|
||||
|
||||
var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
|
||||
$("#palette-container .palette_node").each(function(i,el) {
|
||||
var currentLabel = $(el).find(".palette_label").text();
|
||||
if (val === "" || re.test(el.id) || re.test(currentLabel)) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
|
||||
for (var category in categoryContainers) {
|
||||
if (categoryContainers.hasOwnProperty(category)) {
|
||||
if (categoryContainers[category].container
|
||||
.find(".palette_node")
|
||||
.filter(function() { return $(this).css('display') !== 'none'}).length === 0) {
|
||||
categoryContainers[category].close();
|
||||
} else {
|
||||
categoryContainers[category].open();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
$(".palette-spinner").show();
|
||||
if (RED.settings.paletteCategories) {
|
||||
RED.settings.paletteCategories.forEach(function(category){
|
||||
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category}));
|
||||
});
|
||||
} else {
|
||||
core.forEach(function(category){
|
||||
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category}));
|
||||
});
|
||||
}
|
||||
|
||||
$("#palette-search-clear").on("click",function(e) {
|
||||
e.preventDefault();
|
||||
$("#palette-search-input").val("");
|
||||
filterChange();
|
||||
$("#palette-search-input").focus();
|
||||
});
|
||||
|
||||
$("#palette-search-input").val("");
|
||||
$("#palette-search-input").on("keyup",function() {
|
||||
filterChange();
|
||||
});
|
||||
|
||||
$("#palette-search-input").on("focus",function() {
|
||||
$("body").one("mousedown",function() {
|
||||
$("#palette-search-input").blur();
|
||||
});
|
||||
});
|
||||
|
||||
$("#palette-collapse-all").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
for (var cat in categoryContainers) {
|
||||
if (categoryContainers.hasOwnProperty(cat)) {
|
||||
categoryContainers[cat].close();
|
||||
}
|
||||
}
|
||||
});
|
||||
$("#palette-expand-all").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
for (var cat in categoryContainers) {
|
||||
if (categoryContainers.hasOwnProperty(cat)) {
|
||||
categoryContainers[cat].open();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
add:addNodeType,
|
||||
remove:removeNodeType,
|
||||
hide:hideNodeType,
|
||||
show:showNodeType,
|
||||
refresh:refreshNodeTypes
|
||||
};
|
||||
})();
|