Skip to content

Flow is a node-based programming language engine written in JavaScript.

Notifications You must be signed in to change notification settings

ZXMushroom63/Flow

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

66 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flow

It's a node editor web app in regular old HTML, CSS and JS.

To use it offline, download the repo as a .zip, extract it, and open flow.html.

If you want to use this, I encourage you to read the code. This helps learn a lot more than the tutorial in README.md.

Changing CSS

All styles a held in the styles.css file. This includes the classes for the nodes, canvas and even buttons.

Adding nodes

  1. First, create a new .js file in the `scripts/input/` folder. Name it anything you like.
  2. To add nodes, you must call the `addNode()` function in the script. Examples below:
  3. Value node (addition, subtraction, etc)
    addNode("addition", {
        alias: ["Add", "addition", "+", "plus"], //Aliases for the node. When searching for nodes in the toolbar, it searches through aliases too. Eg: division, divide, /, div
        title: "Add", //Title for the node. Defaults to first entry in alias
        renameable: false, //Can the title of the node be renamed in editor
        inputs: ["A", "B"], //The node's inputs
        outputs: ["sum"], //The node's outputs
        func: function (a, b) {
    
            return [parseFloat(a) + parseFloat(b)]; //Return an array, with the array corresponding to outputs.
    
        }, //The node's behaviour. If you want to interact with the node's div element, use the this keyword
        color: "darkred", //The CSS color of the node's title. I personally find that dark___ colors work quite well. Defaults to 'darkcyan'.
        doc: "Adds A and B together", //The documentation for this node. Can be accessed by opening the documentation page by clicking the '?' button.
    });
    Logic node (wait, alert, etc)
    addNode("wait", {
        command: true, //Is the node a command node (Does it have an execution flow),
        hat: false, //Is the node a hat node (Does it start command nodes when triggered) [needs `command: true`]
        alias: ["wait", "sleep"], //Aliases for the node. When searching for nodes in the toolbar, it searches through aliases too. Eg: division, divide, /, div
        title: "Wait n seconds", //Title for the node. Defaults to first entry in alias
        renameable: false, //Can the title of the node be renamed in editor
        inputs: ["n"], //The node's inputs
        outputs: ["⏩"], //The node's outputs. To add output executionary branches, add entries that contain the ⏩ text character. If an invalid entry is detected, 'outputs' is ignored.
        func: function (n) {
            return new Promise((resolve, reject)=>{
                setTimeout(()=>{
    
                    resolve([0]); //Resolve with an array of branches to run, in order.
    
                }, parseFloat(n) * 1000)
            });
        }, //The node's behaviour. If you want to interact with the node's div element, use the this keyword
        color: "darkcyan", //The CSS color of the node's title. I personally find that dark___ colors work quite well. Defaults to 'darkcyan'.
        doc: "Waits for n seconds.", //The documentation for this node. Can be accessed by opening the documentation page by clicking the '?' button.
    });
    Logic node (if, sequence, branch, etc)
    addNode("ifelse", {
        command: true, //Is the node a command node (Does it have an execution flow),
        hat: false, //Is the node a hat node (Does it start command nodes when triggered) [needs `command: true`]
        alias: ["if then else", "otherwise"], //Aliases for the node. When searching for nodes in the toolbar, it searches through aliases too. Eg: division, divide, /, div
        title: "If Else", //Title for the node. Defaults to first entry in alias
        renameable: false, //Can the title of the node be renamed in editor
        inputs: ["x"], //The node's inputs
        outputs: ["⏩A", "⏩B"], //The node's outputs. To add output executionary branches, add entries that contain the ⏩ text character. If an invalid entry is detected, 'outputs' is ignored.
        func: function (x) {
            return new Promise((resolve, reject)=>{
                if (Boolean.valueOf(x) === true) {
                    resolve([0]);
                } else {
                    resolve([1]);
                }
            });
        }, //The node's behaviour. If you want to interact with the node's div element, use the this keyword
        color: "darkcyan", //The CSS color of the node's title. I personally find that dark___ colors work quite well. Defaults to 'darkcyan'.
        doc: "If x is true, runs A. Otherwise, runs B.", //The documentation for this node. Can be accessed by opening the documentation page by clicking the '?' button.
    });
    Hat node (start, tick, when blah, etc)
    addNode("a_key_event", {
        command: true, //Is the node a command node (Does it have an execution flow),
        hat: true, //Is the node a hat node (Does it start command nodes when triggered) [needs `command: true`]
        alias: ["When A key pressed"], //Aliases for the node. When searching for nodes in the toolbar, it searches through aliases too. Eg: division, divide, /, div
        renameable: false, //Can the title of the node be renamed in editor
        func: function (x) {
            return new Promise((resolve, reject)=>{
                resolve([0]);
            });
        }, //The node's behaviour. If you want to interact with the node's div element, use the this keyword
        color: "darkcyan", //The CSS color of the node's title. I personally find that dark___ colors work quite well. Defaults to 'darkcyan'.
        doc: "Starts whenever the A key is pressed.", //The documentation for this node. Can be accessed by opening the documentation page by clicking the '?' button.
    });
    window.addEventListener("keydown", (e)=>{
        if (e.key.toLowerCase() === "a") {
            startHats("a_key_event");
        }
    })
  4. Link the script in flow.html, where the comment indicates. (near the bottom of the file.)

Running code

This is actually quite simple: Just find the node element (has the node class), and run either getValue() or exec() on it, depending on whether it's a command/hat node or a value node. Eg:

var node = document.querySelector(".node");
node.getValue(); //Returns an array of outputs

//OR, if it is a command node:

node.exec(); //Will start running the execution flow from this node.

This, by nature, is far slower than if the nodes are first compiled in to an abstract graph before usage.

Serialising, deserialising and exporting code.

Flow has built in functions for serialising, deserialising and exporting graphs made in the editor.

Run serialise() to serialise on-screen nodes. (Returns an object representing the workspace). Run deserialise(serialised) to load the serialised object.

Run compileGraph(nodeElement, false) to generate an abstract graph that can be used to run and evaluate the nodes much faster. Eg:

var node = document.querySelector(".node");
var graph = compileGraph(node, false);

graph.getValue(); //Returns an array of outputs

//OR, if it is a command node:

graph.exec(); //Will start running the execution flow from this node.

To disable interaction with the node elements in compileGraph, simply pass true as the second argument.

Run stringifyGraph(graph) to stringify a graph.

Run translateGraph(graph, "nameOfFunction") to further optimise a graph and stringify it into a single function. (does not support command/hat nodes);