This commit is contained in:
tamat
2023-04-11 08:58:01 +02:00
16 changed files with 11667 additions and 163 deletions

View File

@@ -1,12 +1,18 @@
{
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 5
},
module.exports = {
"env": {
"browser": true,
"node": true
"es2021": true,
"node": true,
"jest/globals": true
},
"extends": "eslint:recommended",
"overrides": [
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["jest"],
"globals": {
"gl": true,
"GL": true,

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@ node_modules/*
npm-debug.log
temp/
temp/*
coverage/
# Editors
/.vscode/*

View File

@@ -176,6 +176,7 @@ You can write any feedback to javi.agenjo@gmail.com
## Contributors
- atlasan
- kriffe
- rappestad
- InventivetalentDev

View File

@@ -5,13 +5,30 @@ LiteGraph.node_images_path = "../nodes_data/";
var editor = new LiteGraph.Editor("main",{miniwindow:false});
window.graphcanvas = editor.graphcanvas;
window.graph = editor.graph;
window.addEventListener("resize", function() { editor.graphcanvas.resize(); } );
updateEditorHiPPICanvas();
window.addEventListener("resize", function() {
editor.graphcanvas.resize();
updateEditorHiPPICanvas();
} );
//window.addEventListener("keydown", editor.graphcanvas.processKey.bind(editor.graphcanvas) );
window.onbeforeunload = function(){
var data = JSON.stringify( graph.serialize() );
localStorage.setItem("litegraphg demo backup", data );
}
function updateEditorHiPPICanvas() {
const ratio = window.devicePixelRatio;
if(ratio == 1) { return }
const rect = editor.canvas.parentNode.getBoundingClientRect();
const { width, height } = rect;
editor.canvas.width = width * ratio;
editor.canvas.height = height * ratio;
editor.canvas.style.width = width + "px";
editor.canvas.style.height = height + "px";
editor.canvas.getContext("2d").scale(ratio, ratio);
return editor.canvas;
}
//enable scripting
LiteGraph.allow_scripts = true;

View File

@@ -25,7 +25,7 @@ LiteGraph.slot_types_out = []; // slot types OUT*/
LiteGraph.alt_drag_do_clone_nodes = true; // [true!] very handy; ALT click to clone and drag the new node
LiteGraph.do_add_triggers_slots = true; // [true!] will create and connect event slots when using action/events connections; !WILL CHANGE node mode when using onTrigger (enable mode colors); onExecuted does not need this
LiteGraph.allow_multi_output_for_events = false; // [false!] being events; it is strongly reccomended to use them sequentually; one by one
LiteGraph.allow_multi_output_for_events = false; // [false!] being events; it is strongly reccomended to use them sequentially; one by one
LiteGraph.middle_click_slot_add_default_node = true; //[true!] allows to create and connect a ndoe clicking with the third button (wheel)
LiteGraph.release_link_on_empty_shows_menu = true; //[true!] dragging a link to empty space will open a menu, add from list, search or defaults
LiteGraph.pointerevents_method = "mouse"; // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now)

View File

@@ -25,7 +25,7 @@ LiteGraph.slot_types_out = []; // slot types OUT*/
LiteGraph.alt_drag_do_clone_nodes = true; // [true!] very handy; ALT click to clone and drag the new node
LiteGraph.do_add_triggers_slots = true; // [true!] will create and connect event slots when using action/events connections; !WILL CHANGE node mode when using onTrigger (enable mode colors); onExecuted does not need this
LiteGraph.allow_multi_output_for_events = false; // [false!] being events; it is strongly reccomended to use them sequentually; one by one
LiteGraph.allow_multi_output_for_events = false; // [false!] being events; it is strongly reccomended to use them sequentially; one by one
LiteGraph.middle_click_slot_add_default_node = true; //[true!] allows to create and connect a ndoe clicking with the third button (wheel)
LiteGraph.release_link_on_empty_shows_menu = true; //[true!] dragging a link to empty space will open a menu, add from list, search or defaults
LiteGraph.pointerevents_method = "pointer"; // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now)

195
jest.config.js Normal file
View File

@@ -0,0 +1,195 @@
/*
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/configuration
*/
module.exports = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/tmp/jest_rs",
// Automatically clear mock calls, instances, contexts and results before every test
// clearMocks: false,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
// coverageProvider: "babel",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// The default configuration for fake timers
// fakeTimers: {
// "enableGlobally": false
// },
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "mjs",
// "cjs",
// "jsx",
// "ts",
// "tsx",
// "json",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: undefined,
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
// testEnvironment: "jest-environment-node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};

11008
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,8 @@
"prebuild": "rimraf build",
"build": "grunt build",
"start": "nodemon utils/server.js",
"test": "echo \"Error: no test specified\" && exit 1",
"test": "jest",
"test:allVersions": "./utils/test.sh",
"prettier": "npx prettier --write src/**/*.* css/**/*.*",
"lint": "npx eslint src",
"lint:fix": "npx eslint --fix src"
@@ -33,12 +34,16 @@
},
"homepage": "https://github.com/jagenjo/litegraph.js#readme",
"devDependencies": {
"@types/jest": "^28.1.3",
"eslint": "^8.37.0 ",
"eslint-plugin-jest": "^27.2.1",
"express": "^4.17.1",
"google-closure-compiler": "^20171112.0.0",
"grunt": "^1.1.0",
"grunt-cli": "^1.2.0",
"grunt-closure-tools": "^1.0.0",
"grunt-contrib-concat": "^1.0.1",
"jest": "^28.1.3",
"nodemon": "^1.19.4",
"rimraf": "^2.7.1"
}

2
src/litegraph.d.ts vendored
View File

@@ -1142,6 +1142,8 @@ export declare class LGraphCanvas {
allow_interaction: boolean;
/** allows to change a connection with having to redo it again */
allow_reconnect_links: boolean;
/** allow selecting multi nodes without pressing extra keys */
multi_select: boolean;
/** No effect */
allow_searchbox: boolean;
always_render_background: boolean;

View File

@@ -124,14 +124,14 @@
registered_slot_out_types: {}, // slot types for nodeclass
slot_types_in: [], // slot types IN
slot_types_out: [], // slot types OUT
slot_types_default_in: [], // specify for each IN slot type a(/many) deafult node(s), use single string, array, or object (with node, title, parameters, ..) like for search
slot_types_default_out: [], // specify for each OUT slot type a(/many) deafult node(s), use single string, array, or object (with node, title, parameters, ..) like for search
slot_types_default_in: [], // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search
slot_types_default_out: [], // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search
alt_drag_do_clone_nodes: false, // [true!] very handy, ALT click to clone and drag the new node
do_add_triggers_slots: false, // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this
allow_multi_output_for_events: true, // [false!] being events, it is strongly reccomended to use them sequentually, one by one
allow_multi_output_for_events: true, // [false!] being events, it is strongly reccomended to use them sequentially, one by one
middle_click_slot_add_default_node: false, //[true!] allows to create and connect a ndoe clicking with the third button (wheel)
@@ -157,80 +157,67 @@
console.log("Node registered: " + type);
}
var categories = type.split("/");
var classname = base_class.name;
const classname = base_class.name;
var pos = type.lastIndexOf("/");
base_class.category = type.substr(0, pos);
const pos = type.lastIndexOf("/");
base_class.category = type.substring(0, pos);
if (!base_class.title) {
base_class.title = classname;
}
//info.name = name.substr(pos+1,name.length - pos);
//extend class
if (base_class.prototype) {
//is a class
for (var i in LGraphNode.prototype) {
if (!base_class.prototype[i]) {
base_class.prototype[i] = LGraphNode.prototype[i];
}
for (var i in LGraphNode.prototype) {
if (!base_class.prototype[i]) {
base_class.prototype[i] = LGraphNode.prototype[i];
}
}
var prev = this.registered_node_types[type];
if(prev)
console.log("replacing node type: " + type);
else
{
if( !Object.hasOwnProperty( base_class.prototype, "shape") )
Object.defineProperty(base_class.prototype, "shape", {
set: function(v) {
switch (v) {
case "default":
delete this._shape;
break;
case "box":
this._shape = LiteGraph.BOX_SHAPE;
break;
case "round":
this._shape = LiteGraph.ROUND_SHAPE;
break;
case "circle":
this._shape = LiteGraph.CIRCLE_SHAPE;
break;
case "card":
this._shape = LiteGraph.CARD_SHAPE;
break;
default:
this._shape = v;
}
},
get: function(v) {
return this._shape;
},
enumerable: true,
configurable: true
});
const prev = this.registered_node_types[type];
if(prev) {
console.log("replacing node type: " + type);
}
if( !Object.prototype.hasOwnProperty.call( base_class.prototype, "shape") ) {
Object.defineProperty(base_class.prototype, "shape", {
set: function(v) {
switch (v) {
case "default":
delete this._shape;
break;
case "box":
this._shape = LiteGraph.BOX_SHAPE;
break;
case "round":
this._shape = LiteGraph.ROUND_SHAPE;
break;
case "circle":
this._shape = LiteGraph.CIRCLE_SHAPE;
break;
case "card":
this._shape = LiteGraph.CARD_SHAPE;
break;
default:
this._shape = v;
}
},
get: function() {
return this._shape;
},
enumerable: true,
configurable: true
});
//warnings
if (base_class.prototype.onPropertyChange) {
console.warn(
"LiteGraph node class " +
type +
" has onPropertyChange method, it must be called onPropertyChanged with d at the end"
);
}
//used to know which nodes create when dragging files to the canvas
if (base_class.supported_extensions) {
for (var i in base_class.supported_extensions) {
var ext = base_class.supported_extensions[i];
if(ext && ext.constructor === String)
this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class;
}
}
}
//used to know which nodes to create when dragging files to the canvas
if (base_class.supported_extensions) {
for (let i in base_class.supported_extensions) {
const ext = base_class.supported_extensions[i];
if(ext && ext.constructor === String) {
this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class;
}
}
}
}
this.registered_node_types[type] = base_class;
if (base_class.constructor.name) {
@@ -251,19 +238,11 @@
" has onPropertyChange method, it must be called onPropertyChanged with d at the end"
);
}
//used to know which nodes create when dragging files to the canvas
if (base_class.supported_extensions) {
for (var i=0; i < base_class.supported_extensions.length; i++) {
var ext = base_class.supported_extensions[i];
if(ext && ext.constructor === String)
this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class;
}
}
// TODO one would want to know input and ouput :: this would allow trought registerNodeAndSlotType to get all the slots types
//console.debug("Registering "+type);
if (this.auto_load_slot_types) nodeTmp = new base_class(base_class.title || "tmpnode");
// TODO one would want to know input and ouput :: this would allow through registerNodeAndSlotType to get all the slots types
if (this.auto_load_slot_types) {
new base_class(base_class.title || "tmpnode");
}
},
/**
@@ -272,13 +251,18 @@
* @param {String|Object} type name of the node or the node constructor itself
*/
unregisterNodeType: function(type) {
var base_class = type.constructor === String ? this.registered_node_types[type] : type;
if(!base_class)
throw("node type not found: " + type );
delete this.registered_node_types[base_class.type];
if(base_class.constructor.name)
delete this.Nodes[base_class.constructor.name];
},
const base_class =
type.constructor === String
? this.registered_node_types[type]
: type;
if (!base_class) {
throw "node type not found: " + type;
}
delete this.registered_node_types[base_class.type];
if (base_class.constructor.name) {
delete this.Nodes[base_class.constructor.name];
}
},
/**
* Save a slot type and his node
@@ -286,38 +270,49 @@
* @param {String|Object} type name of the node or the node constructor itself
* @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, ..
*/
registerNodeAndSlotType: function(type,slot_type,out){
registerNodeAndSlotType: function(type, slot_type, out){
out = out || false;
var base_class = type.constructor === String && this.registered_node_types[type] !== "anonymous" ? this.registered_node_types[type] : type;
var sCN = base_class.constructor.type;
if (typeof slot_type == "string"){
var aTypes = slot_type.split(",");
}else if (slot_type == this.EVENT || slot_type == this.ACTION){
var aTypes = ["_event_"];
}else{
var aTypes = ["*"];
const base_class =
type.constructor === String &&
this.registered_node_types[type] !== "anonymous"
? this.registered_node_types[type]
: type;
const class_type = base_class.constructor.type;
let allTypes = [];
if (typeof slot_type === "string") {
allTypes = slot_type.split(",");
} else if (slot_type == this.EVENT || slot_type == this.ACTION) {
allTypes = ["_event_"];
} else {
allTypes = ["*"];
}
for (var i = 0; i < aTypes.length; ++i) {
var sT = aTypes[i]; //.toLowerCase();
if (sT === ""){
sT = "*";
for (let i = 0; i < allTypes.length; ++i) {
let slotType = allTypes[i];
if (slotType === "") {
slotType = "*";
}
var registerTo = out ? "registered_slot_out_types" : "registered_slot_in_types";
if (typeof this[registerTo][sT] == "undefined") this[registerTo][sT] = {nodes: []};
this[registerTo][sT].nodes.push(sCN);
const registerTo = out
? "registered_slot_out_types"
: "registered_slot_in_types";
if (this[registerTo][slotType] === undefined) {
this[registerTo][slotType] = { nodes: [] };
}
if (!this[registerTo][slotType].nodes.includes(class_type)) {
this[registerTo][slotType].nodes.push(class_type);
}
// check if is a new type
if (!out){
if (!this.slot_types_in.includes(sT.toLowerCase())){
this.slot_types_in.push(sT.toLowerCase());
if (!out) {
if (!this.slot_types_in.includes(slotType.toLowerCase())) {
this.slot_types_in.push(slotType.toLowerCase());
this.slot_types_in.sort();
}
}else{
if (!this.slot_types_out.includes(sT.toLowerCase())){
this.slot_types_out.push(sT.toLowerCase());
} else {
if (!this.slot_types_out.includes(slotType.toLowerCase())) {
this.slot_types_out.push(slotType.toLowerCase());
this.slot_types_out.sort();
}
}
@@ -1262,35 +1257,35 @@
LGraph.prototype.arrange = function (margin, layout) {
margin = margin || 100;
var nodes = this.computeExecutionOrder(false, true);
var columns = [];
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
var col = node._level || 1;
const nodes = this.computeExecutionOrder(false, true);
const columns = [];
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i];
const col = node._level || 1;
if (!columns[col]) {
columns[col] = [];
}
columns[col].push(node);
}
var x = margin;
let x = margin;
for (var i = 0; i < columns.length; ++i) {
var column = columns[i];
for (let i = 0; i < columns.length; ++i) {
const column = columns[i];
if (!column) {
continue;
}
var max_size = 100;
var y = margin + LiteGraph.NODE_TITLE_HEIGHT;
for (var j = 0; j < column.length; ++j) {
var node = column[j];
let max_size = 100;
let y = margin + LiteGraph.NODE_TITLE_HEIGHT;
for (let j = 0; j < column.length; ++j) {
const node = column[j];
node.pos[0] = (layout == LiteGraph.VERTICAL_LAYOUT) ? y : x;
node.pos[1] = (layout == LiteGraph.VERTICAL_LAYOUT) ? x : y;
var max_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 1 : 0;
const max_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 1 : 0;
if (node.size[max_size_index] > max_size) {
max_size = node.size[max_size_index];
}
var node_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 0 : 1;
const node_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 0 : 1;
y += node.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT;
}
x += max_size + margin;
@@ -5211,6 +5206,7 @@ LGraphNode.prototype.executeAction = function(action)
this.allow_dragcanvas = true;
this.allow_dragnodes = true;
this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc
this.multi_select = false; //allow selecting multi nodes without pressing extra keys
this.allow_searchbox = true;
this.allow_reconnect_links = true; //allows to change a connection with having to redo it again
this.align_to_grid = false; //snap to grid
@@ -5436,7 +5432,7 @@ LGraphNode.prototype.executeAction = function(action)
};
/**
* returns the visualy active graph (in case there are more in the stack)
* returns the visually active graph (in case there are more in the stack)
* @method getCurrentGraph
* @return {LGraph} the active graph
*/
@@ -6061,9 +6057,13 @@ LGraphNode.prototype.executeAction = function(action)
this.graph.beforeChange();
this.node_dragged = node;
}
if (!this.selected_nodes[node.id]) {
this.processNodeSelected(node, e);
}
this.processNodeSelected(node, e);
} else { // double-click
/**
* Don't call the function if the block is already selected.
* Otherwise, it could cause the block to be unselected while its panel is open.
*/
if (!node.is_selected) this.processNodeSelected(node, e);
}
this.dirty_canvas = true;
@@ -6475,6 +6475,10 @@ LGraphNode.prototype.executeAction = function(action)
var n = this.selected_nodes[i];
n.pos[0] += delta[0] / this.ds.scale;
n.pos[1] += delta[1] / this.ds.scale;
if (!n.is_selected) this.processNodeSelected(n, e); /*
* Don't call the function if the block is already selected.
* Otherwise, it could cause the block to be unselected while dragging.
*/
}
this.dirty_canvas = true;
@@ -7004,9 +7008,9 @@ LGraphNode.prototype.executeAction = function(action)
}
}
if (e.code == "KeyV" && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
if (e.code == "KeyV" && (e.metaKey || e.ctrlKey)) {
//paste
this.pasteFromClipboard();
this.pasteFromClipboard(e.shiftKey);
}
//delete or backspace
@@ -7091,15 +7095,15 @@ LGraphNode.prototype.executeAction = function(action)
var target_node = this.graph.getNodeById(
link_info.origin_id
);
if (!target_node || !this.selected_nodes[target_node.id]) {
//improve this by allowing connections to non-selected nodes
if (!target_node) {
continue;
} //not selected
}
clipboard_info.links.push([
target_node._relative_id,
link_info.origin_slot, //j,
node._relative_id,
link_info.target_slot
link_info.target_slot,
target_node.id
]);
}
}
@@ -7110,7 +7114,7 @@ LGraphNode.prototype.executeAction = function(action)
);
};
LGraphCanvas.prototype.pasteFromClipboard = function() {
LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) {
var data = localStorage.getItem("litegrapheditor_clipboard");
if (!data) {
return;
@@ -7159,7 +7163,16 @@ LGraphNode.prototype.executeAction = function(action)
//create links
for (var i = 0; i < clipboard_info.links.length; ++i) {
var link_info = clipboard_info.links[i];
var origin_node = nodes[link_info[0]];
var origin_node;
var origin_node_relative_id = link_info[0];
if (origin_node_relative_id != null) {
origin_node = nodes[origin_node_relative_id];
} else if (isConnectUnselected) {
var origin_node_id = link_info[4];
if (origin_node_id) {
origin_node = this.graph.getNodeById(origin_node_id);
}
}
var target_node = nodes[link_info[2]];
if( origin_node && target_node )
origin_node.connect(link_info[1], target_node, link_info[3]);
@@ -7288,7 +7301,7 @@ LGraphNode.prototype.executeAction = function(action)
};
LGraphCanvas.prototype.processNodeSelected = function(node, e) {
this.selectNode(node, e && (e.shiftKey||e.ctrlKey));
this.selectNode(node, e && (e.shiftKey || e.ctrlKey || this.multi_select));
if (this.onNodeSelected) {
this.onNodeSelected(node);
}
@@ -7324,6 +7337,7 @@ LGraphNode.prototype.executeAction = function(action)
for (var i in nodes) {
var node = nodes[i];
if (node.is_selected) {
this.deselectNode(node);
continue;
}
@@ -9743,13 +9757,17 @@ LGraphNode.prototype.executeAction = function(action)
ctx.fillRect(margin, y, widget_width - margin * 2, H);
var range = w.options.max - w.options.min;
var nvalue = (w.value - w.options.min) / range;
ctx.fillStyle = active_widget == w ? "#89A" : "#678";
if(nvalue < 0.0) nvalue = 0.0;
if(nvalue > 1.0) nvalue = 1.0;
ctx.fillStyle = w.options.hasOwnProperty("slider_color") ? w.options.slider_color : (active_widget == w ? "#89A" : "#678");
ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H);
if(show_text && !w.disabled)
ctx.strokeRect(margin, y, widget_width - margin * 2, H);
if (w.marker) {
var marker_nvalue = (w.marker - w.options.min) / range;
ctx.fillStyle = "#AA9";
if(marker_nvalue < 0.0) marker_nvalue = 0.0;
if(marker_nvalue > 1.0) marker_nvalue = 1.0;
ctx.fillStyle = w.options.hasOwnProperty("marker_color") ? w.options.marker_color : "#AA9";
ctx.fillRect( margin + marker_nvalue * (widget_width - margin * 2), y, 2, H );
}
if (show_text) {
@@ -9916,6 +9934,7 @@ LGraphNode.prototype.executeAction = function(action)
case "slider":
var range = w.options.max - w.options.min;
var nvalue = Math.clamp((x - 15) / (widget_width - 30), 0, 1);
if(w.options.read_only) break;
w.value = w.options.min + (w.options.max - w.options.min) * nvalue;
if (w.callback) {
setTimeout(function() {
@@ -9996,6 +10015,12 @@ LGraphNode.prototype.executeAction = function(action)
var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0;
if (event.click_time < 200 && delta == 0) {
this.prompt("Value",w.value,function(v) {
// check if v is a valid equation or a number
if (/^[0-9+\-*/()\s]+$/.test(v)) {
try {//solve the equation if possible
v = eval(v);
} catch (e) { }
}
this.value = Number(v);
inner_value_change(this, this.value);
}.bind(w),
@@ -10024,7 +10049,6 @@ LGraphNode.prototype.executeAction = function(action)
case "text":
if (event.type == LiteGraph.pointerevents_method+"down") {
this.prompt("Value",w.value,function(v) {
this.value = v;
inner_value_change(this, v);
}.bind(w),
event,w.options ? w.options.multiline : false );
@@ -10049,6 +10073,9 @@ LGraphNode.prototype.executeAction = function(action)
}//end for
function inner_value_change(widget, value) {
if(widget.type == "number"){
value = Number(value);
}
widget.value = value;
if ( widget.options && widget.options.property && node.properties[widget.options.property] !== undefined ) {
node.setProperty( widget.options.property, value );
@@ -11167,7 +11194,7 @@ LGraphNode.prototype.executeAction = function(action)
LGraphCanvas.search_limit = -1;
LGraphCanvas.prototype.showSearchBox = function(event, options) {
// proposed defaults
def_options = { slot_from: null
var def_options = { slot_from: null
,node_from: null
,node_to: null
,do_type_filter: LiteGraph.search_filter_enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out
@@ -11865,7 +11892,7 @@ LGraphNode.prototype.executeAction = function(action)
// TODO refactor, theer are different dialog, some uses createDialog, some dont
LGraphCanvas.prototype.createDialog = function(html, options) {
def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true };
var def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true };
options = Object.assign(def_options, options || {});
var dialog = document.createElement("div");
@@ -12288,7 +12315,7 @@ LGraphNode.prototype.executeAction = function(action)
var ref_window = this.getCanvasWindow();
var that = this;
var graphcanvas = this;
panel = this.createPanel(node.title || "",{
var panel = this.createPanel(node.title || "",{
closable: true
,window: ref_window
,onOpen: function(){

226
src/litegraph.test.js Normal file
View File

@@ -0,0 +1,226 @@
describe("register node types", () => {
let lg;
let Sum;
beforeEach(() => {
jest.resetModules();
lg = require("./litegraph");
Sum = function Sum() {
this.addInput("a", "number");
this.addInput("b", "number");
this.addOutput("sum", "number");
};
Sum.prototype.onExecute = function (a, b) {
this.setOutputData(0, a + b);
};
});
afterEach(() => {
jest.restoreAllMocks();
});
test("normal case", () => {
lg.LiteGraph.registerNodeType("math/sum", Sum);
let node = lg.LiteGraph.registered_node_types["math/sum"];
expect(node).toBeTruthy();
expect(node.type).toBe("math/sum");
expect(node.title).toBe("Sum");
expect(node.category).toBe("math");
expect(node.prototype.configure).toBe(
lg.LGraphNode.prototype.configure
);
});
test("callback triggers", () => {
const consoleSpy = jest
.spyOn(console, "log")
.mockImplementation(() => {});
lg.LiteGraph.onNodeTypeRegistered = jest.fn();
lg.LiteGraph.onNodeTypeReplaced = jest.fn();
lg.LiteGraph.registerNodeType("math/sum", Sum);
expect(lg.LiteGraph.onNodeTypeRegistered).toHaveBeenCalled();
expect(lg.LiteGraph.onNodeTypeReplaced).not.toHaveBeenCalled();
lg.LiteGraph.registerNodeType("math/sum", Sum);
expect(lg.LiteGraph.onNodeTypeReplaced).toHaveBeenCalled();
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringMatching("replacing node type")
);
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringMatching("math/sum")
);
});
test("node with title", () => {
Sum.title = "The sum title";
lg.LiteGraph.registerNodeType("math/sum", Sum);
let node = lg.LiteGraph.registered_node_types["math/sum"];
expect(node.title).toBe("The sum title");
expect(node.title).not.toBe(node.name);
});
test("handle error simple object", () => {
expect(() =>
lg.LiteGraph.registerNodeType("math/sum", { simple: "type" })
).toThrow("Cannot register a simple object");
});
test("check shape mapping", () => {
lg.LiteGraph.registerNodeType("math/sum", Sum);
const node_type = lg.LiteGraph.registered_node_types["math/sum"];
expect(new node_type().shape).toBe(undefined);
node_type.prototype.shape = "default";
expect(new node_type().shape).toBe(undefined);
node_type.prototype.shape = "box";
expect(new node_type().shape).toBe(lg.LiteGraph.BOX_SHAPE);
node_type.prototype.shape = "round";
expect(new node_type().shape).toBe(lg.LiteGraph.ROUND_SHAPE);
node_type.prototype.shape = "circle";
expect(new node_type().shape).toBe(lg.LiteGraph.CIRCLE_SHAPE);
node_type.prototype.shape = "card";
expect(new node_type().shape).toBe(lg.LiteGraph.CARD_SHAPE);
node_type.prototype.shape = "custom_shape";
expect(new node_type().shape).toBe("custom_shape");
// Check that also works for replaced node types
jest.spyOn(console, "log").mockImplementation(() => {});
function NewCalcSum(a, b) {
return a + b;
}
lg.LiteGraph.registerNodeType("math/sum", NewCalcSum);
const new_node_type = lg.LiteGraph.registered_node_types["math/sum"];
new_node_type.prototype.shape = "box";
expect(new new_node_type().shape).toBe(lg.LiteGraph.BOX_SHAPE);
});
test("onPropertyChanged warning", () => {
const consoleSpy = jest
.spyOn(console, "warn")
.mockImplementation(() => {});
Sum.prototype.onPropertyChange = true;
lg.LiteGraph.registerNodeType("math/sum", Sum);
expect(consoleSpy).toBeCalledTimes(1);
expect(consoleSpy).toBeCalledWith(
expect.stringContaining("has onPropertyChange method")
);
expect(consoleSpy).toBeCalledWith(expect.stringContaining("math/sum"));
});
test("registering supported file extensions", () => {
expect(lg.LiteGraph.node_types_by_file_extension).toEqual({});
// Create two node types with calc_times overriding .pdf
Sum.supported_extensions = ["PDF", "exe", null];
function Times() {
this.addInput("a", "number");
this.addInput("b", "number");
this.addOutput("times", "number");
}
Times.prototype.onExecute = function (a, b) {
this.setOutputData(0, a * b);
};
Times.supported_extensions = ["pdf", "jpg"];
lg.LiteGraph.registerNodeType("math/sum", Sum);
lg.LiteGraph.registerNodeType("math/times", Times);
expect(
Object.keys(lg.LiteGraph.node_types_by_file_extension).length
).toBe(3);
expect(lg.LiteGraph.node_types_by_file_extension).toHaveProperty("pdf");
expect(lg.LiteGraph.node_types_by_file_extension).toHaveProperty("exe");
expect(lg.LiteGraph.node_types_by_file_extension).toHaveProperty("jpg");
expect(lg.LiteGraph.node_types_by_file_extension.exe).toBe(Sum);
expect(lg.LiteGraph.node_types_by_file_extension.pdf).toBe(Times);
expect(lg.LiteGraph.node_types_by_file_extension.jpg).toBe(Times);
});
test("register in/out slot types", () => {
expect(lg.LiteGraph.registered_slot_in_types).toEqual({});
expect(lg.LiteGraph.registered_slot_out_types).toEqual({});
// Test slot type registration with first type
lg.LiteGraph.auto_load_slot_types = true;
lg.LiteGraph.registerNodeType("math/sum", Sum);
expect(lg.LiteGraph.registered_slot_in_types).toEqual({
number: { nodes: ["math/sum"] },
});
expect(lg.LiteGraph.registered_slot_out_types).toEqual({
number: { nodes: ["math/sum"] },
});
// Test slot type registration with second type
function ToInt() {
this.addInput("string", "string");
this.addOutput("number", "number");
};
ToInt.prototype.onExecute = function (str) {
this.setOutputData(0, Number(str));
};
lg.LiteGraph.registerNodeType("basic/to_int", ToInt);
expect(lg.LiteGraph.registered_slot_in_types).toEqual({
number: { nodes: ["math/sum"] },
string: { nodes: ["basic/to_int"] },
});
expect(lg.LiteGraph.registered_slot_out_types).toEqual({
number: { nodes: ["math/sum", "basic/to_int"] },
});
});
});
describe("unregister node types", () => {
let lg;
let Sum;
beforeEach(() => {
jest.resetModules();
lg = require("./litegraph");
Sum = function Sum() {
this.addInput("a", "number");
this.addInput("b", "number");
this.addOutput("sum", "number");
};
Sum.prototype.onExecute = function (a, b) {
this.setOutputData(0, a + b);
};
});
afterEach(() => {
jest.restoreAllMocks();
});
test("remove by name", () => {
lg.LiteGraph.registerNodeType("math/sum", Sum);
expect(lg.LiteGraph.registered_node_types["math/sum"]).toBeTruthy();
lg.LiteGraph.unregisterNodeType("math/sum");
expect(lg.LiteGraph.registered_node_types["math/sum"]).toBeFalsy();
});
test("remove by object", () => {
lg.LiteGraph.registerNodeType("math/sum", Sum);
expect(lg.LiteGraph.registered_node_types["math/sum"]).toBeTruthy();
lg.LiteGraph.unregisterNodeType(Sum);
expect(lg.LiteGraph.registered_node_types["math/sum"]).toBeFalsy();
});
test("try removing with wrong name", () => {
expect(() => lg.LiteGraph.unregisterNodeType("missing/type")).toThrow(
"node type not found: missing/type"
);
});
test("no constructor name", () => {
function BlankNode() {}
BlankNode.constructor = {}
lg.LiteGraph.registerNodeType("blank/node", BlankNode);
expect(lg.LiteGraph.registered_node_types["blank/node"]).toBeTruthy()
lg.LiteGraph.unregisterNodeType("blank/node");
expect(lg.LiteGraph.registered_node_types["blank/node"]).toBeFalsy();
})
});

View File

@@ -363,7 +363,7 @@
return -1;
if(l == 1)
return 0;
//dichotimic search
//dichotomic search
while (imax >= imin)
{
imid = ((imax + imin)*0.5)|0;

View File

@@ -5338,7 +5338,7 @@ void main(void){\n\
}
gl.finish2D();
});
else //rendering to offscren canvas and uploading to texture
else //rendering to offscreen canvas and uploading to texture
{
if(properties.clear)
ctx.clearRect(0,0,canvas.width,canvas.height);

View File

@@ -93,10 +93,10 @@
logicAnd.title = "AND";
logicAnd.desc = "Return true if all inputs are true";
logicAnd.prototype.onExecute = function() {
ret = true;
for (inX in this.inputs){
var ret = true;
for (var inX in this.inputs){
if (!this.getInputData(inX)){
ret = false;
var ret = false;
break;
}
}
@@ -119,8 +119,8 @@
logicOr.title = "OR";
logicOr.desc = "Return true if at least one input is true";
logicOr.prototype.onExecute = function() {
ret = false;
for (inX in this.inputs){
var ret = false;
for (var inX in this.inputs){
if (this.getInputData(inX)){
ret = true;
break;
@@ -159,9 +159,9 @@
logicCompare.title = "bool == bool";
logicCompare.desc = "Compare for logical equality";
logicCompare.prototype.onExecute = function() {
last = null;
ret = true;
for (inX in this.inputs){
var last = null;
var ret = true;
for (var inX in this.inputs){
if (last === null) last = this.getInputData(inX);
else
if (last != this.getInputData(inX)){

16
utils/test.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -eo pipefail
cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
export NVM_DIR=$HOME/.nvm
source "$NVM_DIR/nvm.sh"
# This are versions 12, 14, 16, 18
NODE_VERSIONS=("lts/erbium" "lts/fermium" "lts/gallium" "lts/hydrogen")
for NODE_VERSION in "${NODE_VERSIONS[@]}"; do
nvm install "$NODE_VERSION"
nvm exec "$NODE_VERSION" npm install
nvm exec "$NODE_VERSION" npm test
done