Refactoring of NodeRed editor using ES6 modules.
npm i -g testcafe http-server n yarn ava
Install latest node version:
n latest
Editor is packaged using Webpack 3
See /webpack
folder. Webpack is configured to use babili to uglify and compress for production.
npm run build:dev
- builds development version: bundle.js
npm run build:prod
- builds production version: bundle.prod.js
src/legacy
contains the old (working) editor code.src/new
contains the new refactored editor code using classes.
src/new/red.js
attempts to reconstruct the global RED
object using these classes.
The refactored code is a WIP and has not yet been tested and (might) lack some "stitching" to recreate a fully working RED
application object.
The source code is written using ES2017 syntax, transpiled to ES5 using babelJS
Please make the new/refactored code work using TDD, ie. with ava unit tests and nightmare E2E tests.
Please note that the editor needs red-api for core functionality.
"dependencies": {
"red-api": "github:tecla5/red-api"
},
- Top bar (top)
- Menu (in right side of top bar)
- Nodes Palette (left panel)
- Sidebar (right panel)
- Flow Canvas area (center)
- Node editor dialogue (model, when node in canvas is clicked)
- Tray (bottom, notifications area)
- Node Diff (modal, via menu)
- User settings (modal, via menu)
Displays logo, main menu and user session state
Displays list of global options/actions, such as load/export nodes etc.
Display all the nodes available to be used on canvas
Displays tabs with information.
The run
tab displays logs when running a flow
Canvas area to draw flows, via nodes and connections
Tray panel form with fields for node properties of the particular node selected. Double clicking a node opens the Node editor dialog.
Custom Nodes can be designed as separate node modules that can be plugged in. The node configuration is used by Node editor to display appropriate form fields to correctly edit each of the node properties as per the configuration.
Tray is used for the node editor dialogue, user settings and node diff. The Node editor, User settings and Node diff are presented in a tray that slides in.
Tray panel with node difference
Tray panel form with user settings
Communications
socket communicationEvents
event notifications (publish/subscribe)I18n
InternationalizationMain
Application loader. Loads nodes, flows and editorSettings
Load/save application settings using local storage and ajax. Includes theming.User
user login/logout and control user menu (ie. session state)Validators
validate number, regex and typed inputs in editorHistory
history action stack (push/pop) with undo/redoNodeConfig
single node configuration (state)NodeRegistry
registry of available nodesNodes
collection of nodesTextFormat
used to format text for url, email etc. for HTML displayBidi
bidirectional text display (RTL - Right then Left, LTR - Left then Right)
See node-red UI API for more details
Actions
actions (create, add, remove, get, list, invoke)Clipboard
clipboard managementDiff
calculate and show nodes differenceEditor
manage and display node editorLibrary
manage node library (load, save, export)Notifications
notify and display notificationsSearch
Search for nodes. Display and manage search dialogSubflow
manage and display subflow of nodesTray
display and manage tray bar (slide-in modal panel, on top of right side of canvas)TypeSearch
search for node by type? Display and manage search dialogUserSettings
display and manage user settings paneWorkspaces
display and manage workspaces (dialogs + tabs)
jQuery widgets
CheckboxSet
jQuery widget to display and manage set (group) of checkboxesEditableList
jQuery widget to display and manage editable list (add/remove items)SearchBox
jquery widget to display and manage search boxTypedInput
jQuery widget to display and manage a typed input (such as numbers only etc.). See validators
Classes
Menu
display and manage menu (in topbar), using menu data from settingsPanel
display panel (including drag bar to resize panels)Popover
display and manage popover (modal notification)Stack
display and manage stack of items (used in node palette)Tabs
display and manage tabs (containing flows of nodes)
PaletteEditor
display and manage palette category (and interaction with canvas via events?)Palette
display and manage full nodes palette with all categories
Sidebar
display and manage sidebar including tabsSidebarTabConfig
display and manage sidebar tab configuration and main behaviorSidebarTabInfo
display and manage tab content, tips etc.
RadialMenu
display and manage D3 radial menu. Activated by long-touch event (for touch devices). Context-specific actions for the current selection - such as delete, edit, copy, paste
The /assets
folder contains the original assets used to generate the main HTML page.
The UI is rendered via mustache templates (with partials) in red-api/src/new/api/ui.js
class Ui {
// ...
_loadSharedPartials() {
var partials = {};
let rootDir = './editor/templates'
var recursiveReadSync = require('recursive-readdir-sync')
var files = recursiveReadSync(rootDir)
for (var i = 0, l = files.length; i < l; i++) {
var file = files[i];
if (file.match(/\.mst$/)) {
var name = path.basename(file, '.mst');
let contents = fs.readFileSync(file, 'utf8');
partials[name] = contents
}
}
return partials;
}
editor(req, res) {
let partials = _loadSharedPartials()
// console.log({
// partials: Object.keys(partials)
// })
let html = Mustache.render(editorTemplate, theme.context(), partials)
res.send(html);
}
}
Note: The legacy node-red code did not use partials.
templates/index.mst
contains a mustache template to create the HTML.
The template needs to be rendered in order to do E2E testing using nightmare (ie. better Jasmine). Alternatively create static pages for testing. The HTML pages rendered must load the javascript built using webpack.
First step is to make the simple (class) refactoring work using original functionality with jQuery etc. This is a current Work in Progress (WIP)
Next step will be to convert each main UI component such as Palette
, Sidebar
etc. into Vue components that can be imported and used in a Vue app.
Each component should be tested individually use Vue best practices.
Theming can be done in the red/api/theme.js
file
The defaultContext
is loaded from ./default-context
. It contains the basic Them "outline", including header
, favicon
, icon
and title
, logo image and more...
We have currently changed:
title
to'App Orchestrator'
const title = 'App Orchestrator'
module.exports = {
page: {
title: title,
favicon: "favicon.ico",
tabicon: "red/images/node-red-icon-black.svg"
},
header: {
title: title,
image: "red/images/node-red.png"
},
asset: {
red: (process.env.NODE_ENV == "development") ? "red/red.es5.js" : "red/red.min.js",
main: (process.env.NODE_ENV == "development") ? "red/main.es5.js" : "red/main.min.js",
}
};
See theme_spec.js
for examples on how to customize theming.
The theme when fully configured is sent to the Mustache template in editor/templates/new/index.mst
<div id="header">
<span class="logo">{{#header.url}}<a href="{{.}}">{{/header.url}}{{#header.image}}<img src="{{.}}" title="{{version}}">{{/header.image}} <span>{{ header.title }}</span>{{#header.url}}</a>{{/header.url}}</span>
<ul class="header-toolbar hide">
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
</ul>
<div id="header-shade" class="hide"></div>
</div>
<div id="main-container" class="sidebar-closed hide">
<div id="workspace">
...
</div>
</div>
Here you can add login/logout buttons and use other custom theming variables as you see fit :)
We should be leveraging Partials for easier re-factoring of the UI.
Partials begin with a greater than sign, like {{> box}}
. The partial will inherit the variables from the calling context
base.mustache
file
<h2>Names</h2>
{{#names}}
{{> user}}
{{/names}}
user.mustache
file
<strong>{{name}}</strong>
Can be thought of as a single, expanded template:
<h2>Names</h2>
{{#names}}
<strong>{{name}}</strong>
{{/names}}
For unit tests use ava test runner.
Run all tests:
$ npm test
Run a particular test:
$ ava test/comms.test.js
Start bottom up, working on the simplest, bottom down classes/modules first, those with less dependencies and less complex code. Gradually work your way bottom up, breadth first.
Currently the following test is a good starting point:
$ ava test/new/text/format/breadcrumb.test.js
E2E (ie. User Acceptance) tests must be written using NightmareJS with modern async/await syntax.
Tests should be written using Page Objects design pattern and should be run via test cafe
Please see How to set up E2E browser testing
See nightmare API
var Nightmare = require('nightmare'),
nightmare = Nightmare({
show: true
});
async function run () {
var result = await nightmare
//load a url
.goto('http://yahoo.com')
//simulate typing into an element identified by a CSS selector
//here, Nightmare is typing into the search bar
.type('input[title="Search"]', 'github nightmare')
//click an element identified by a CSS selector
//in this case, click the search button
.click('#uh-search-button')
//wait for an element identified by a CSS selector
//in this case, the body of the results
.wait('#main')
//execute javascript on the page
//here, the function is getting the HREF of the first search result
.evaluate(function() {
return document.querySelector('#main .searchCenterMiddle li a')
.href;
});
//queue and end the Nightmare instance along with the Electron instance it wraps
await nightmare.end();
console.log(result);
};
run();
You can also run nightmare tests with ava
test runner using ava assertions.
import Nightmare from 'nightmare';
import test from 'ava';
test('should find the nightmare github link first', async t => {
const nightmare = Nightmare()
let link = await nightmare
.goto('https://duckduckgo.com')
.type('#search_form_input_homepage', 'github nightmare')
.click('#search_button_homepage')
.wait('#zero_click_wrapper .c-info__title a')
.evaluate(() =>
document.querySelector('#zero_click_wrapper .c-info__title a').href
)
.end()
// test link found
t.is(link, 'https://github.com/segmentio/nightmare')
})