How to register custom nodes on different engines:
Blackprint is registered on window object
If you're using TypeScript, you can also use Blackprint.registerInterface and Blackprint.registerNode as a decorator for your class.
@Blackprint.registerNode("Nodes/...")
class YourNodeName extends Blackprint.Node { ... }
@Blackprint.registerInterface("BPIC/Nodes/...")
class YourIFaceName extends Blackprint.Interface { ... }
Blackprint.Sketch.registerInterface("BPIC/Nodes/...", YourIFaceName);
Register Interface/Node
This can be exact same with the Deno/Node.js version. On browser, if you want to register interface you must also register the Sketch Interface.
// Define your node structure and place your code to handle the interface here
// With compatibility in mind (can be imported for Browser/Node.js)
Blackprint.registerNode('Example/Nodes/Button',
class YourNodeName extends Blackprint.Node {
// Define output ports for this node
static output = {
Clicked: Function // This will be connected to other node's input port if exist
};
// Define input ports for this node
// static input = { ThePortName: DataTypeHere };
constructor(instance){
super(instance);
// Let's use 'BPIC/Nodes/Button interface for this node
let iface = this.setInterface('BPIC/Nodes/Button');
iface.title = "My Button"; // Custom node title
}
// Triggered from 'BPIC/Nodes/Button' interface
clicked(ev){
console.log('Example/Nodes/Button: got', ev);
node.outputs.Clicked(ev);
}
});
// Registering Interface is optional
// If you think you don't need a custom interface
// Then using registerNode is enough
// Place your code that interacting with your library or Node.js API here
class YourIFaceName extends Blackprint.Interface {
constructor(node){
super(node);
this.yourCustomProperty = '...';
}
clicked(ev){
console.log("Engine: 'Trigger' button clicked, going to run the handler");
// Event route: iface.clicked -> node.clicked -> node.outputs.Clicked
this.node.clicked && this.node.clicked(ev);
}
};
Blackprint.registerInterface('BPIC/Nodes/Button', YourIFaceName);
// Register custom HTML interface for sketch
Blackprint.Sketch.Interface('BPIC/Nodes/Button', {
html: `
<div class="node {{ type || 'general' }}" style="transform: translate({{ x }}px, {{ y }}px)">
<sf-template path="Blackprint/nodes/template/header.sf"></sf-template>
<div class="content">
<div class="left-port">
<sf-template path="Blackprint/nodes/template/input-port.sf"></sf-template>
</div>
<div style="display: inline-block; color: yellow">
{{ yourCustomProperty }}
</div>
<div class="right-port">
<sf-template path="Blackprint/nodes/template/output-port.sf"></sf-template>
</div>
</div>
</div>`
}, YourIFaceName);
// --- How to use it ---
var sketch = new Blackprint.Sketch();
let button = sketch.createNode('Example/Nodes/Button', {x: 100, y: 200});
// button is instance of (class YourIFaceName extends Blackprint.Interface)
button.clicked("'An event'"); // == iface.clicked(...)
// --- Console output ---
//> Engine: 'Trigger' button clicked, going to run the handler
//> Example/Nodes/SimpleButton: got 'An event'Blackprint is registered on window object
Blackprint is registered on window object
If you're using TypeScript, you can also use Blackprint.registerInterface and Blackprint.registerNode as a decorator for your class.
@Blackprint.registerNode("Nodes/...")
class YourNodeName extends Blackprint.Node { ... }
@Blackprint.registerInterface("BPIC/Nodes/...")
class YourIFaceName extends Blackprint.Interface { ... }
Blackprint.Sketch.registerInterface("BPIC/Nodes/...", YourIFaceName);
Register Interface/Node
This can be exact same with the browser version. You don't need to register Blackprint.Sketch.registerInterface because Node.js and Deno doesn't need to user Sketch Interface.
// For Deno
import { Engine } from 'https://cdn.skypack.dev/@blackprint/engine@0.6';
// For Node.js
const { Engine } = require('@blackprint/engine');
// Place your code that interacting with your library or Node.js API here
Blackprint.registerInterface('BPIC/Nodes/Button',
class YourIFaceName extends Blackprint.Interface {
constructor(node){
super(node);
this.yourCustomProperty = '...';
}
clicked(ev){
console.log("Engine: 'Trigger' button clicked, going to run the handler");
// Event route: iface.clicked -> node.clicked -> node.outputs.Clicked
this.node.clicked && this.node.clicked(ev);
}
});
// Define your node structure and place your code to handle the interface here
// With compatibility in mind (can be imported for Browser/Node.js)
Blackprint.registerNode('Example/Nodes/Button',
class YourNodeName extends Blackprint.Node {
// Define output ports for this node
static outputs = {
Clicked: Function // This will be connected to other node's input port if exist
};
// Define input ports for this node
// static input = { ThePortName: DataTypeHere };
constructor(instance){
super(instance);
// Let's use 'BPIC/Nodes/Button interface for this node
let iface = this.setInterface('BPIC/Nodes/Button');
iface.title = "My Button"; // Custom node title
}
// Triggered from 'BPIC/Nodes/Button' interface
clicked(ev){
console.log('Example/Nodes/Button: got', ev);
node.outputs.Clicked(ev);
}
});
// --- How to use it ---
var instance = new Engine();
var button = instance.createNode("Example/Nodes/Button");
// button is instance of (class YourIFaceName extends Blackprint.Interface)
button.clicked("'An event'"); // == iface.clicked(...)
// --- Console output ---
//> Engine: 'Trigger' button clicked, going to run the handler
//> Example/Nodes/Button: got 'An event'
When registering with Namespace, the default root namespace is always "BPNode". Please name your nodes namespace along with your library name to avoid conflict with other library.
Entrypoint
require_once('../vendor/autoload.php');
// This can be called on different PHP libraries
\Blackprint\registerNamespace(__DIR__.'/BPNode');
Different file
// file: ./BPNode/Example/Nodes/Hello.php
namespace \BPNode\Example\Nodes;
use \Blackprint\{
Engine,
Types,
};
// The class name must match with the file name
// This will be registered as Node definition
class Hello extends \Blackprint\Node {
function __construct($instance){
// Call the parent constructor first, passing the $instance (Blackprint\Engine)
parent::__construct($instance);
// Set the Interface, let it empty if you want
// to use default empty interface "setInterface()"
$iface = $this->setInterface('BPIC/Nodes/Button');
$iface->title = "Button"; // Set the title for debugging
// Please remember to capitalize the port name
// Set the output port structure for your node (Optional)
$this->output = [
'Clicked'=> Blackprint\Types::Function,
];
// Set the input port structure for your node (Optional)
// $this->input = [ 'PortName' => TypeData ];
}
// Proxy event object from: iface.clicked -> node.clicked -> outputs.Clicked
clicked($ev){
colorLog("Nodes/Hello: got $ev");
$node->outputs['Clicked']($ev);
}
}
// Your Interface namespace must use "BPIC" as the prefix
\Blackprint\registerInterface('BPIC\Nodes\Button', HelloIFace::class);
class HelloIFace extends \Blackprint\Interfaces {
function __construct($node){
// Call the parent constructor first, passing the $node (Blackprint\Node)
parent::__construct($node);
// $this->node => Blackprint\Node
}
clicked($ev){
colorLog("Engine: 'Trigger' button clicked, going to run the handler");
isset($iface->node->clicked) && ($iface->node->clicked)($ev);
}
}
$instance = new Engine;
// --- How to use it ---
$button = instance.createNode("Example/Nodes/Button");
echo "\n\n>> I'm clicking the button";
($button->clicked)("'An event'");
// --- Console output ---
//> Engine: 'Trigger' button clicked, going to run the handler
//> Example/Nodes/Button: got 'An event'
# Work in progress
Before we get started, it would be better if you understand how we will implement the nodes.
There are 2 type of registration:
Register
Information
Role
Node
Data and type assignment
Will be used like an object to store and obtaining data from IFace
Interface/IFace
System/Browser interface
Will be used for User Interface on the browser or interface for interacting with System/Library/Browser API.
Below is the flow visualization and description:
Blackprint Engine will load JSON and create every Node from it by using the registered node (registerNode) in the engine. The created Node will just act like an object for storing data, validating, or do some data processing. The Node will search for it's interface assigned in iface.interface that was registered (registerInterface) on the engine, if it was undefined the default node interface will be used.
The IFace will bind itself with the Node, and both of them can interact from the object reference. Blackprint Engine will handle the data flow for every Node, and the custom IFace will handle the data events from both System or Node's data changes.
Maybe it's better to try with some example or experimenting if you still confused.
Alright, let's clone these repository.
$ git clone --depth 1 https://github.com/Blackprint/Blackprint.git .
# Create symbolic link to Blackprint's dist folder
# (Windows: may need administrator privileges)
$ mklink /D ".\example\dist" "..\dist" # For Windows
# For linux
$ ln -s dist ./example
# Install the dependency from ./Blackprint/package.json
$ npm i
Starting the server and the compiler
$ npm start
# The compiled engine-js and nodes will be placed on ./dist folder
# You will also need to add your new custom node's .js, .css on /example/public/index.html
Modifying the code
You can modify /gulpfile.js for customize the compiler. Currently I'm figuring a better way to make the development more easier, so this may be changed.
The /example/public folder have default index.html for getting started, and your css and js should be written into /example/src or /src folder and your browser should being refreshed automatically.
The compiler already have file timestamp versioning to avoid browser cache, so you don't need to press CTRL+F5 every time you modify your code in /src.
Compiling the code
The compilation process will minify your code and also run Babel transpiler to support low end browser.