mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-01 11:42:06 +00:00
Add ESLint, pre-commit hook & format all files (#319)
* Add ESLint config * Add ESLint packages * Add prettier config * Fix ESLint package version * Format all files * Format static assets * Format project root config * Add pre-commit code formatting Formats .css & .js files automatically. If any .ts or .mts files are staged, the entire project is type-checked. Packages: - lint-staged - husky - prettier
This commit is contained in:
@@ -1 +0,0 @@
|
||||
/build
|
||||
56
.eslintrc.js
56
.eslintrc.js
@@ -1,56 +0,0 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true,
|
||||
"jest/globals": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"overrides": [
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["jest"],
|
||||
"globals": {
|
||||
"gl": true,
|
||||
"GL": true,
|
||||
"LS": true,
|
||||
"Uint8Array": true,
|
||||
"Uint32Array": true,
|
||||
"Float32Array": true,
|
||||
"LGraphCanvas": true,
|
||||
"LGraph": true,
|
||||
"LGraphNode": true,
|
||||
"LiteGraph": true,
|
||||
"LGraphTexture": true,
|
||||
"Mesh": true,
|
||||
"Shader": true,
|
||||
"enableWebGLCanvas": true,
|
||||
"vec2": true,
|
||||
"vec3": true,
|
||||
"vec4": true,
|
||||
"DEG2RAD": true,
|
||||
"isPowerOfTwo": true,
|
||||
"cloneCanvas": true,
|
||||
"createCanvas": true,
|
||||
"hex2num": true,
|
||||
"colorToString": true,
|
||||
"showElement": true,
|
||||
"quat": true,
|
||||
"AudioSynth": true,
|
||||
"SillyClient": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"no-empty": "warn",
|
||||
"no-redeclare": "warn",
|
||||
"no-inner-declarations": "warn",
|
||||
"no-constant-condition": "warn",
|
||||
"no-unused-vars": "warn",
|
||||
"no-mixed-spaces-and-tabs": "warn",
|
||||
"no-unreachable": "warn",
|
||||
"curly": ["warn", "all"]
|
||||
}
|
||||
}
|
||||
5
.husky/pre-commit
Normal file
5
.husky/pre-commit
Normal file
@@ -0,0 +1,5 @@
|
||||
if [[ "$OS" == "Windows_NT" ]]; then
|
||||
npx.cmd lint-staged
|
||||
else
|
||||
npx lint-staged
|
||||
fi
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"singleQuote": false,
|
||||
"semi": true,
|
||||
"tabWidth": 2
|
||||
"semi": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all",
|
||||
"overrides": [{ "files": "*.ts", "options": { "requirePragma": true } }],
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
|
||||
158
eslint.config.js
Normal file
158
eslint.config.js
Normal file
@@ -0,0 +1,158 @@
|
||||
import globals from "globals"
|
||||
import eslint from "@eslint/js"
|
||||
import tseslint from "typescript-eslint"
|
||||
import stylistic from "@stylistic/eslint-plugin"
|
||||
|
||||
export default tseslint.config(
|
||||
{ files: ["**/*.{js,mjs,ts,mts}"] },
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
stylistic.configs.customize({
|
||||
quotes: "double",
|
||||
braceStyle: "1tbs",
|
||||
commaDangle: "always-multiline",
|
||||
}),
|
||||
{
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser },
|
||||
parserOptions: {
|
||||
projectService: {
|
||||
allowDefaultProject: [
|
||||
"eslint.config.js",
|
||||
"lint-staged.config.js",
|
||||
"vite.config.mts",
|
||||
],
|
||||
},
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ["./dist/**/*"],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
// TODO: Update when TypeScript has been cleaned
|
||||
"prefer-spread": "off",
|
||||
"no-empty": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"no-var": "warn",
|
||||
"no-fallthrough": "off",
|
||||
|
||||
"no-empty-pattern": ["error", { allowObjectPatternsAsParameters: true }],
|
||||
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||
"@typescript-eslint/no-unsafe-argument": "off",
|
||||
"@typescript-eslint/no-unsafe-call": "off",
|
||||
"@typescript-eslint/no-unsafe-return": "off",
|
||||
|
||||
"@typescript-eslint/no-base-to-string": "off",
|
||||
"@typescript-eslint/restrict-plus-operands": "off",
|
||||
"@typescript-eslint/no-implied-eval": "off",
|
||||
"@typescript-eslint/unbound-method": "off",
|
||||
"@typescript-eslint/no-unsafe-enum-comparison": "off",
|
||||
"@typescript-eslint/no-for-in-array": "off",
|
||||
"@typescript-eslint/only-throw-error": "off",
|
||||
"@typescript-eslint/no-duplicate-type-constituents": "off",
|
||||
"@typescript-eslint/no-empty-object-type": "off",
|
||||
|
||||
// "@typescript-eslint/prefer-readonly-parameter-types": "error",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
// "@typescript-eslint/no-unsafe-function-type": "off",
|
||||
|
||||
"@stylistic/max-len": [
|
||||
"warn",
|
||||
{ code: 100, comments: 130, ignoreStrings: true },
|
||||
],
|
||||
|
||||
// "@stylistic/multiline-comment-style": ["warn", "starred-block"],
|
||||
"@stylistic/curly-newline": [
|
||||
"warn",
|
||||
{ consistent: true, multiline: true },
|
||||
],
|
||||
"@stylistic/object-curly-newline": [
|
||||
"warn",
|
||||
{ consistent: true, multiline: true },
|
||||
],
|
||||
// "@stylistic/object-property-newline": ["warn", { allowAllPropertiesOnSameLine: true }],
|
||||
// "@stylistic/object-property-newline": "warn",
|
||||
"@stylistic/one-var-declaration-per-line": "warn",
|
||||
|
||||
"@stylistic/array-bracket-newline": ["warn", { multiline: true }],
|
||||
"@stylistic/array-element-newline": [
|
||||
"warn",
|
||||
{ consistent: true, multiline: true },
|
||||
],
|
||||
|
||||
"@stylistic/function-paren-newline": ["warn", "multiline-arguments"],
|
||||
"@stylistic/newline-per-chained-call": "warn",
|
||||
|
||||
"@stylistic/array-bracket-spacing": "warn",
|
||||
"@stylistic/arrow-parens": "warn",
|
||||
"@stylistic/arrow-spacing": "warn",
|
||||
"@stylistic/block-spacing": "warn",
|
||||
"@stylistic/brace-style": "warn",
|
||||
"@stylistic/comma-dangle": "warn",
|
||||
"@stylistic/comma-spacing": "warn",
|
||||
"@stylistic/comma-style": "warn",
|
||||
"@stylistic/computed-property-spacing": "warn",
|
||||
"@stylistic/dot-location": "warn",
|
||||
"@stylistic/eol-last": "warn",
|
||||
"@stylistic/indent": ["warn", 2, { VariableDeclarator: "first" }],
|
||||
"@stylistic/indent-binary-ops": "warn",
|
||||
"@stylistic/key-spacing": "warn",
|
||||
"@stylistic/keyword-spacing": "warn",
|
||||
"@stylistic/lines-between-class-members": "warn",
|
||||
"@stylistic/max-statements-per-line": "warn",
|
||||
"@stylistic/member-delimiter-style": "warn",
|
||||
"@stylistic/multiline-ternary": "warn",
|
||||
"@stylistic/new-parens": "warn",
|
||||
"@stylistic/no-extra-parens": "warn",
|
||||
"@stylistic/no-floating-decimal": "warn",
|
||||
"@stylistic/no-mixed-operators": "warn",
|
||||
"@stylistic/no-mixed-spaces-and-tabs": "warn",
|
||||
"@stylistic/no-multi-spaces": "warn",
|
||||
"@stylistic/no-multiple-empty-lines": "warn",
|
||||
"@stylistic/no-tabs": "warn",
|
||||
"@stylistic/no-trailing-spaces": "warn",
|
||||
"@stylistic/no-whitespace-before-property": "warn",
|
||||
"@stylistic/object-curly-spacing": "warn",
|
||||
"@stylistic/operator-linebreak": [
|
||||
"warn",
|
||||
"after",
|
||||
{ overrides: { "?": "before", ":": "before" } },
|
||||
],
|
||||
"@stylistic/padded-blocks": "warn",
|
||||
"@stylistic/quote-props": "warn",
|
||||
"@stylistic/quotes": "warn",
|
||||
"@stylistic/rest-spread-spacing": "warn",
|
||||
"@stylistic/semi": "warn",
|
||||
"@stylistic/semi-spacing": "warn",
|
||||
"@stylistic/semi-style": ["warn", "first"],
|
||||
"@stylistic/space-before-blocks": "warn",
|
||||
"@stylistic/space-before-function-paren": "warn",
|
||||
"@stylistic/space-in-parens": "warn",
|
||||
"@stylistic/space-infix-ops": "warn",
|
||||
"@stylistic/space-unary-ops": "warn",
|
||||
"@stylistic/spaced-comment": "warn",
|
||||
"@stylistic/template-curly-spacing": "warn",
|
||||
"@stylistic/template-tag-spacing": "warn",
|
||||
"@stylistic/type-annotation-spacing": "warn",
|
||||
"@stylistic/type-generic-spacing": "warn",
|
||||
"@stylistic/type-named-tuple-spacing": "warn",
|
||||
"@stylistic/wrap-iife": "warn",
|
||||
"@stylistic/yield-star-spacing": "warn",
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
},
|
||||
files: ["test/**/*.ts"],
|
||||
},
|
||||
)
|
||||
17
lint-staged.config.js
Normal file
17
lint-staged.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
export default {
|
||||
"*.css": stagedFiles => `prettier --write ${stagedFiles.join(" ")}`,
|
||||
|
||||
"*.js": stagedFiles => prettierAndEslint(stagedFiles),
|
||||
|
||||
"*.{ts,mts}": stagedFiles => [
|
||||
...prettierAndEslint(stagedFiles),
|
||||
`tsc --noEmit`,
|
||||
],
|
||||
}
|
||||
|
||||
function prettierAndEslint(fileNames) {
|
||||
return [
|
||||
`prettier --write ${fileNames.join(" ")}`,
|
||||
`eslint --fix ${fileNames.join(" ")}`,
|
||||
]
|
||||
}
|
||||
2219
package-lock.json
generated
2219
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -41,10 +41,19 @@
|
||||
},
|
||||
"homepage": "https://github.com/Comfy-Org/litegraph.js",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.14.0",
|
||||
"@stylistic/eslint-plugin": "^2.10.1",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/node": "^22.1.0",
|
||||
"eslint": "^9.14.0",
|
||||
"globals": "^15.12.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^25.0.1",
|
||||
"lint-staged": "^15.2.10",
|
||||
"prettier": "^3.3.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.14.0",
|
||||
"vite": "^5.3.4",
|
||||
"vite-plugin-dts": "^4.3.0",
|
||||
"vitest": "^2.1.4"
|
||||
|
||||
@@ -207,14 +207,14 @@
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.litegraph.lite-search-item.not_in_filter{
|
||||
.litegraph.lite-search-item.not_in_filter {
|
||||
/*background-color: rgba(50, 50, 50, 0.5);*/
|
||||
/*color: #999;*/
|
||||
color: #B99;
|
||||
color: #b99;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.litegraph.lite-search-item.generic_type{
|
||||
.litegraph.lite-search-item.generic_type {
|
||||
/*background-color: rgba(50, 50, 50, 0.5);*/
|
||||
/*color: #DD9;*/
|
||||
color: #999;
|
||||
@@ -230,7 +230,7 @@
|
||||
|
||||
.litegraph.lite-search-item-type {
|
||||
display: inline-block;
|
||||
background: rgba(0,0,0,0.2);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
margin-left: 5px;
|
||||
font-size: 14px;
|
||||
padding: 2px 5px;
|
||||
@@ -238,7 +238,7 @@
|
||||
top: -2px;
|
||||
opacity: 0.8;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/* DIALOGs ******/
|
||||
|
||||
@@ -249,7 +249,7 @@
|
||||
margin-top: -150px;
|
||||
margin-left: -200px;
|
||||
|
||||
background-color: #2A2A2A;
|
||||
background-color: #2a2a2a;
|
||||
|
||||
min-width: 400px;
|
||||
min-height: 200px;
|
||||
@@ -260,7 +260,7 @@
|
||||
.litegraph .dialog.settings {
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
height: calc( 100% - 20px );
|
||||
height: calc(100% - 20px);
|
||||
margin: auto;
|
||||
max-width: 50%;
|
||||
}
|
||||
@@ -272,7 +272,7 @@
|
||||
transform: translateX(-50%);
|
||||
min-width: 600px;
|
||||
min-height: 300px;
|
||||
height: calc( 100% - 100px );
|
||||
height: calc(100% - 100px);
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@@ -289,12 +289,18 @@
|
||||
}
|
||||
|
||||
.litegraph .dialog .dialog-header {
|
||||
color: #AAA;
|
||||
color: #aaa;
|
||||
border-bottom: 1px solid #161616;
|
||||
}
|
||||
|
||||
.litegraph .dialog .dialog-header { height: 40px; }
|
||||
.litegraph .dialog .dialog-footer { height: 50px; padding: 10px; border-top: 1px solid #1a1a1a;}
|
||||
.litegraph .dialog .dialog-header {
|
||||
height: 40px;
|
||||
}
|
||||
.litegraph .dialog .dialog-footer {
|
||||
height: 50px;
|
||||
padding: 10px;
|
||||
border-top: 1px solid #1a1a1a;
|
||||
}
|
||||
|
||||
.litegraph .dialog .dialog-header .dialog-title {
|
||||
font: 20px "Arial";
|
||||
@@ -303,12 +309,13 @@
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.litegraph .dialog .dialog-content, .litegraph .dialog .dialog-alt-content {
|
||||
.litegraph .dialog .dialog-content,
|
||||
.litegraph .dialog .dialog-alt-content {
|
||||
height: calc(100% - 90px);
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
display: inline-block;
|
||||
color: #AAA;
|
||||
color: #aaa;
|
||||
/*background-color: black;*/
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -342,7 +349,7 @@
|
||||
|
||||
.litegraph .dialog .separator {
|
||||
display: block;
|
||||
width: calc( 100% - 4px );
|
||||
width: calc(100% - 4px);
|
||||
height: 1px;
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #333;
|
||||
@@ -377,10 +384,10 @@
|
||||
.litegraph .dialog .property_value {
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
color: #AAA;
|
||||
background-color: #1A1A1A;
|
||||
color: #aaa;
|
||||
background-color: #1a1a1a;
|
||||
/*width: calc( 100% - 122px );*/
|
||||
max-width: calc( 100% - 162px );
|
||||
max-width: calc(100% - 162px);
|
||||
min-width: 200px;
|
||||
max-height: 300px;
|
||||
min-height: 20px;
|
||||
@@ -397,16 +404,16 @@
|
||||
|
||||
.litegraph .dialog .property.boolean .property_value {
|
||||
padding-right: 30px;
|
||||
color: #A88;
|
||||
color: #a88;
|
||||
/*width: auto;
|
||||
float: right;*/
|
||||
}
|
||||
|
||||
.litegraph .dialog .property.boolean.bool-on .property_name{
|
||||
color: #8A8;
|
||||
.litegraph .dialog .property.boolean.bool-on .property_name {
|
||||
color: #8a8;
|
||||
}
|
||||
.litegraph .dialog .property.boolean.bool-on .property_value{
|
||||
color: #8A8;
|
||||
.litegraph .dialog .property.boolean.bool-on .property_value {
|
||||
color: #8a8;
|
||||
}
|
||||
|
||||
.litegraph .dialog .btn {
|
||||
@@ -420,11 +427,11 @@
|
||||
|
||||
.litegraph .dialog .btn:hover {
|
||||
background-color: #111;
|
||||
color: #FFF;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.litegraph .dialog .btn.delete:hover {
|
||||
background-color: #F33;
|
||||
background-color: #f33;
|
||||
color: black;
|
||||
}
|
||||
|
||||
@@ -460,7 +467,7 @@
|
||||
.litegraph .subgraph_property input {
|
||||
width: 140px;
|
||||
color: #999;
|
||||
background-color: #1A1A1A;
|
||||
background-color: #1a1a1a;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
margin-right: 10px;
|
||||
@@ -645,49 +652,48 @@
|
||||
min-height: 0;
|
||||
}
|
||||
.litegraph .dialog .dialog-content {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
.litegraph .dialog .dialog-content .subgraph_property {
|
||||
padding: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
.litegraph .dialog .dialog-footer {
|
||||
margin: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.litegraph .dialog .dialog-footer .subgraph_property {
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
}
|
||||
.litegraph .dialog .dialog-footer .subgraph_property .name {
|
||||
flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
.litegraph .graphdialog {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 20px;
|
||||
padding: 4px 10px;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 20px;
|
||||
padding: 4px 10px;
|
||||
position: fixed;
|
||||
}
|
||||
.litegraph .graphdialog .name {
|
||||
padding: 0;
|
||||
min-height: 0;
|
||||
font-size: 16px;
|
||||
vertical-align: middle;
|
||||
padding: 0;
|
||||
min-height: 0;
|
||||
font-size: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.litegraph .graphdialog .value {
|
||||
font-size: 16px;
|
||||
min-height: 0;
|
||||
margin: 0 10px;
|
||||
padding: 2px 5px;
|
||||
font-size: 16px;
|
||||
min-height: 0;
|
||||
margin: 0 10px;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
.litegraph .graphdialog input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.litegraph .graphdialog button {
|
||||
padding: 4px 18px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
padding: 4px 18px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,12 @@ export class CanvasPointer {
|
||||
static get maxClickDrift() {
|
||||
return this.#maxClickDrift
|
||||
}
|
||||
|
||||
static set maxClickDrift(value) {
|
||||
this.#maxClickDrift = value
|
||||
this.#maxClickDrift2 = value * value
|
||||
}
|
||||
|
||||
static #maxClickDrift = 6
|
||||
/** {@link maxClickDrift} squared. Used to calculate click drift without `sqrt`. */
|
||||
static #maxClickDrift2 = this.#maxClickDrift ** 2
|
||||
@@ -107,6 +109,7 @@ export class CanvasPointer {
|
||||
get finally() {
|
||||
return this.#finally
|
||||
}
|
||||
|
||||
set finally(value) {
|
||||
try {
|
||||
this.#finally?.()
|
||||
@@ -114,6 +117,7 @@ export class CanvasPointer {
|
||||
this.#finally = value
|
||||
}
|
||||
}
|
||||
|
||||
#finally?: () => unknown
|
||||
|
||||
constructor(element: Element) {
|
||||
|
||||
@@ -30,12 +30,13 @@ export class ContextMenu {
|
||||
current_submenu?: ContextMenu
|
||||
lock?: boolean
|
||||
|
||||
// TODO: Interface for values requires functionality change - currently accepts an array of strings, functions, objects, nulls, or undefined.
|
||||
// TODO: Interface for values requires functionality change - currently accepts
|
||||
// an array of strings, functions, objects, nulls, or undefined.
|
||||
constructor(values: (IContextMenuValue | string)[], options: IContextMenuOptions) {
|
||||
options ||= {}
|
||||
this.options = options
|
||||
|
||||
//to link a menu with its parent
|
||||
// to link a menu with its parent
|
||||
const parent = options.parentMenu
|
||||
if (parent) {
|
||||
if (!(parent instanceof ContextMenu)) {
|
||||
@@ -51,13 +52,15 @@ export class ContextMenu {
|
||||
}
|
||||
}
|
||||
|
||||
//use strings because comparing classes between windows doesnt work
|
||||
// use strings because comparing classes between windows doesnt work
|
||||
const eventClass = options.event
|
||||
? options.event.constructor.name
|
||||
: null
|
||||
if (eventClass !== "MouseEvent" &&
|
||||
if (
|
||||
eventClass !== "MouseEvent" &&
|
||||
eventClass !== "CustomEvent" &&
|
||||
eventClass !== "PointerEvent") {
|
||||
eventClass !== "PointerEvent"
|
||||
) {
|
||||
console.error(`Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. (${eventClass})`)
|
||||
options.event = null
|
||||
}
|
||||
@@ -72,44 +75,47 @@ export class ContextMenu {
|
||||
root.style.pointerEvents = "none"
|
||||
setTimeout(function () {
|
||||
root.style.pointerEvents = "auto"
|
||||
}, 100) //delay so the mouse up event is not caught by this element
|
||||
}, 100) // delay so the mouse up event is not caught by this element
|
||||
|
||||
//this prevents the default context browser menu to open in case this menu was created when pressing right button
|
||||
LiteGraph.pointerListenerAdd(root, "up",
|
||||
// this prevents the default context browser menu to open in case this menu was created when pressing right button
|
||||
LiteGraph.pointerListenerAdd(
|
||||
root,
|
||||
"up",
|
||||
function (e: MouseEvent) {
|
||||
//console.log("pointerevents: ContextMenu up root prevent");
|
||||
// console.log("pointerevents: ContextMenu up root prevent");
|
||||
e.preventDefault()
|
||||
return true
|
||||
},
|
||||
true
|
||||
true,
|
||||
)
|
||||
root.addEventListener(
|
||||
"contextmenu",
|
||||
function (e: MouseEvent) {
|
||||
//right button
|
||||
// right button
|
||||
if (e.button != 2) return false
|
||||
e.preventDefault()
|
||||
return false
|
||||
},
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
LiteGraph.pointerListenerAdd(root, "down",
|
||||
LiteGraph.pointerListenerAdd(
|
||||
root,
|
||||
"down",
|
||||
(e: MouseEvent) => {
|
||||
//console.log("pointerevents: ContextMenu down");
|
||||
// console.log("pointerevents: ContextMenu down");
|
||||
if (e.button == 2) {
|
||||
this.close()
|
||||
e.preventDefault()
|
||||
return true
|
||||
}
|
||||
},
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
function on_mouse_wheel(e: WheelEvent) {
|
||||
const pos = parseInt(root.style.top)
|
||||
root.style.top =
|
||||
(pos + e.deltaY * options.scroll_speed).toFixed() + "px"
|
||||
root.style.top = (pos + e.deltaY * options.scroll_speed).toFixed() + "px"
|
||||
e.preventDefault()
|
||||
return true
|
||||
}
|
||||
@@ -122,7 +128,7 @@ export class ContextMenu {
|
||||
|
||||
this.root = root
|
||||
|
||||
//title
|
||||
// title
|
||||
if (options.title) {
|
||||
const element = document.createElement("div")
|
||||
element.className = "litemenu-title"
|
||||
@@ -130,7 +136,7 @@ export class ContextMenu {
|
||||
root.appendChild(element)
|
||||
}
|
||||
|
||||
//entries
|
||||
// entries
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const value = values[i]
|
||||
let name = Array.isArray(values) ? value : String(i)
|
||||
@@ -150,7 +156,7 @@ export class ContextMenu {
|
||||
}
|
||||
})
|
||||
|
||||
//insert before checking position
|
||||
// insert before checking position
|
||||
const ownerDocument = (options.event?.target as Node).ownerDocument
|
||||
const root_document = ownerDocument || document
|
||||
|
||||
@@ -159,7 +165,7 @@ export class ContextMenu {
|
||||
else
|
||||
root_document.body.appendChild(root)
|
||||
|
||||
//compute best position
|
||||
// compute best position
|
||||
let left = options.left || 0
|
||||
let top = options.top || 0
|
||||
if (options.event) {
|
||||
@@ -186,11 +192,14 @@ export class ContextMenu {
|
||||
root.style.left = left + "px"
|
||||
root.style.top = top + "px"
|
||||
|
||||
if (options.scale)
|
||||
root.style.transform = `scale(${options.scale})`
|
||||
if (options.scale) root.style.transform = `scale(${options.scale})`
|
||||
}
|
||||
|
||||
addItem(name: string, value: IContextMenuValue | string, options: IContextMenuOptions): HTMLElement {
|
||||
addItem(
|
||||
name: string,
|
||||
value: IContextMenuValue | string,
|
||||
options: IContextMenuOptions,
|
||||
): HTMLElement {
|
||||
options ||= {}
|
||||
|
||||
const element: ContextMenuDivElement = document.createElement("div")
|
||||
@@ -216,8 +225,7 @@ export class ContextMenu {
|
||||
element.setAttribute("aria-haspopup", "true")
|
||||
element.setAttribute("aria-expanded", "false")
|
||||
}
|
||||
if (value.className)
|
||||
element.className += " " + value.className
|
||||
if (value.className) element.className += " " + value.className
|
||||
}
|
||||
element.value = value
|
||||
element.setAttribute("role", "menuitem")
|
||||
@@ -249,21 +257,26 @@ export class ContextMenu {
|
||||
const value = this.value
|
||||
if (!value || !(value as IContextMenuValue).has_submenu) return
|
||||
|
||||
//if it is a submenu, autoopen like the item was clicked
|
||||
// if it is a submenu, autoopen like the item was clicked
|
||||
inner_onclick.call(this, e)
|
||||
setAriaExpanded()
|
||||
}
|
||||
|
||||
//menu option clicked
|
||||
// menu option clicked
|
||||
const that = this
|
||||
function inner_onclick(this: ContextMenuDivElement, e: MouseEvent) {
|
||||
const value = this.value
|
||||
let close_parent = true
|
||||
|
||||
that.current_submenu?.close(e)
|
||||
if ((value as IContextMenuValue)?.has_submenu || (value as IContextMenuValue)?.submenu) setAriaExpanded()
|
||||
if (
|
||||
(value as IContextMenuValue)?.has_submenu ||
|
||||
(value as IContextMenuValue)?.submenu
|
||||
) {
|
||||
setAriaExpanded()
|
||||
}
|
||||
|
||||
//global callback
|
||||
// global callback
|
||||
if (options.callback) {
|
||||
const r = options.callback.call(
|
||||
this,
|
||||
@@ -271,30 +284,31 @@ export class ContextMenu {
|
||||
options,
|
||||
e,
|
||||
that,
|
||||
options.node
|
||||
options.node,
|
||||
)
|
||||
if (r === true) close_parent = false
|
||||
}
|
||||
|
||||
//special cases
|
||||
// special cases
|
||||
if (typeof value === "object") {
|
||||
if (value.callback &&
|
||||
if (
|
||||
value.callback &&
|
||||
!options.ignore_item_callbacks &&
|
||||
value.disabled !== true) {
|
||||
//item callback
|
||||
value.disabled !== true
|
||||
) {
|
||||
// item callback
|
||||
const r = value.callback.call(
|
||||
this,
|
||||
value,
|
||||
options,
|
||||
e,
|
||||
that,
|
||||
options.extra
|
||||
options.extra,
|
||||
)
|
||||
if (r === true) close_parent = false
|
||||
}
|
||||
if (value.submenu) {
|
||||
if (!value.submenu.options)
|
||||
throw "ContextMenu submenu needs options"
|
||||
if (!value.submenu.options) throw "ContextMenu submenu needs options"
|
||||
|
||||
new that.constructor(value.submenu.options, {
|
||||
callback: value.submenu.callback,
|
||||
@@ -303,14 +317,13 @@ export class ContextMenu {
|
||||
ignore_item_callbacks: value.submenu.ignore_item_callbacks,
|
||||
title: value.submenu.title,
|
||||
extra: value.submenu.extra,
|
||||
autoopen: options.autoopen
|
||||
autoopen: options.autoopen,
|
||||
})
|
||||
close_parent = false
|
||||
}
|
||||
}
|
||||
|
||||
if (close_parent && !that.lock)
|
||||
that.close()
|
||||
if (close_parent && !that.lock) that.close()
|
||||
}
|
||||
|
||||
return element
|
||||
@@ -323,31 +336,38 @@ export class ContextMenu {
|
||||
this.parentMenu.current_submenu = null
|
||||
if (e === undefined) {
|
||||
this.parentMenu.close()
|
||||
} else if (e &&
|
||||
!ContextMenu.isCursorOverElement(e, this.parentMenu.root)) {
|
||||
ContextMenu.trigger(this.parentMenu.root, LiteGraph.pointerevents_method + "leave", e)
|
||||
} else if (e && !ContextMenu.isCursorOverElement(e, this.parentMenu.root)) {
|
||||
ContextMenu.trigger(
|
||||
this.parentMenu.root,
|
||||
LiteGraph.pointerevents_method + "leave",
|
||||
e,
|
||||
)
|
||||
}
|
||||
}
|
||||
this.current_submenu?.close(e, true)
|
||||
|
||||
if (this.root.closing_timer)
|
||||
clearTimeout(this.root.closing_timer)
|
||||
if (this.root.closing_timer) clearTimeout(this.root.closing_timer)
|
||||
}
|
||||
|
||||
//this code is used to trigger events easily (used in the context menu mouseleave
|
||||
static trigger(element: HTMLDivElement, event_name: string, params: MouseEvent, origin?: unknown): CustomEvent {
|
||||
// this code is used to trigger events easily (used in the context menu mouseleave
|
||||
static trigger(
|
||||
element: HTMLDivElement,
|
||||
event_name: string,
|
||||
params: MouseEvent,
|
||||
origin?: unknown,
|
||||
): CustomEvent {
|
||||
const evt = document.createEvent("CustomEvent")
|
||||
evt.initCustomEvent(event_name, true, true, params) //canBubble, cancelable, detail
|
||||
evt.initCustomEvent(event_name, true, true, params) // canBubble, cancelable, detail
|
||||
// @ts-expect-error
|
||||
evt.srcElement = origin
|
||||
if (element.dispatchEvent) element.dispatchEvent(evt)
|
||||
// @ts-expect-error
|
||||
else if (element.__events) element.__events.dispatchEvent(evt)
|
||||
//else nothing seems binded here so nothing to do
|
||||
// else nothing seems binded here so nothing to do
|
||||
return evt
|
||||
}
|
||||
|
||||
//returns the top most menu
|
||||
// returns the top most menu
|
||||
getTopMenu(): ContextMenu {
|
||||
return this.options.parentMenu
|
||||
? this.options.parentMenu.getTopMenu()
|
||||
@@ -360,16 +380,21 @@ export class ContextMenu {
|
||||
: this.options.event
|
||||
}
|
||||
|
||||
static isCursorOverElement(event: MouseEvent, element: HTMLDivElement): boolean {
|
||||
static isCursorOverElement(
|
||||
event: MouseEvent,
|
||||
element: HTMLDivElement,
|
||||
): boolean {
|
||||
const left = event.clientX
|
||||
const top = event.clientY
|
||||
const rect = element.getBoundingClientRect()
|
||||
if (!rect) return false
|
||||
|
||||
if (top > rect.top &&
|
||||
if (
|
||||
top > rect.top &&
|
||||
top < rect.top + rect.height &&
|
||||
left > rect.left &&
|
||||
left < rect.left + rect.width) {
|
||||
left < rect.left + rect.width
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Point, Rect } from "./interfaces"
|
||||
import { clamp, LGraphCanvas } from "./litegraph"
|
||||
import { distance } from "./measure"
|
||||
|
||||
//used by some widgets to render a curve editor
|
||||
// used by some widgets to render a curve editor
|
||||
|
||||
export class CurveEditor {
|
||||
points: Point[]
|
||||
@@ -17,32 +17,39 @@ export class CurveEditor {
|
||||
this.points = points
|
||||
this.selected = -1
|
||||
this.nearest = -1
|
||||
this.size = null //stores last size used
|
||||
this.size = null // stores last size used
|
||||
this.must_update = true
|
||||
this.margin = 5
|
||||
}
|
||||
|
||||
static sampleCurve(f: number, points: Point[]): number {
|
||||
if (!points)
|
||||
return
|
||||
if (!points) return
|
||||
|
||||
for (let i = 0; i < points.length - 1; ++i) {
|
||||
const p = points[i]
|
||||
const pn = points[i + 1]
|
||||
if (pn[0] < f)
|
||||
continue
|
||||
const r = (pn[0] - p[0])
|
||||
if (Math.abs(r) < 0.00001)
|
||||
return p[1]
|
||||
if (pn[0] < f) continue
|
||||
|
||||
const r = pn[0] - p[0]
|
||||
if (Math.abs(r) < 0.00001) return p[1]
|
||||
|
||||
const local_f = (f - p[0]) / r
|
||||
return p[1] * (1.0 - local_f) + pn[1] * local_f
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
draw(ctx: CanvasRenderingContext2D, size: Rect, graphcanvas?: LGraphCanvas, background_color?: string, line_color?: string, inactive = false): void {
|
||||
draw(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
size: Rect,
|
||||
graphcanvas?: LGraphCanvas,
|
||||
background_color?: string,
|
||||
line_color?: string,
|
||||
inactive = false,
|
||||
): void {
|
||||
const points = this.points
|
||||
if (!points)
|
||||
return
|
||||
if (!points) return
|
||||
|
||||
this.size = size
|
||||
const w = size[0] - this.margin * 2
|
||||
const h = size[1] - this.margin * 2
|
||||
@@ -61,8 +68,7 @@ export class CurveEditor {
|
||||
ctx.strokeRect(0, 0, w, h)
|
||||
}
|
||||
ctx.strokeStyle = line_color
|
||||
if (inactive)
|
||||
ctx.globalAlpha = 0.5
|
||||
if (inactive) ctx.globalAlpha = 0.5
|
||||
ctx.beginPath()
|
||||
for (let i = 0; i < points.length; ++i) {
|
||||
const p = points[i]
|
||||
@@ -73,7 +79,9 @@ export class CurveEditor {
|
||||
if (!inactive)
|
||||
for (let i = 0; i < points.length; ++i) {
|
||||
const p = points[i]
|
||||
ctx.fillStyle = this.selected == i ? "#FFF" : (this.nearest == i ? "#DDD" : "#AAA")
|
||||
ctx.fillStyle = this.selected == i
|
||||
? "#FFF"
|
||||
: this.nearest == i ? "#DDD" : "#AAA"
|
||||
ctx.beginPath()
|
||||
ctx.arc(p[0] * w, (1.0 - p[1]) * h, 2, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
@@ -81,61 +89,70 @@ export class CurveEditor {
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
//localpos is mouse in curve editor space
|
||||
// localpos is mouse in curve editor space
|
||||
onMouseDown(localpos: Point, graphcanvas: LGraphCanvas): boolean {
|
||||
const points = this.points
|
||||
if (!points)
|
||||
return
|
||||
if (localpos[1] < 0)
|
||||
return
|
||||
if (!points) return
|
||||
if (localpos[1] < 0) return
|
||||
|
||||
//this.captureInput(true);
|
||||
// this.captureInput(true);
|
||||
const w = this.size[0] - this.margin * 2
|
||||
const h = this.size[1] - this.margin * 2
|
||||
const x = localpos[0] - this.margin
|
||||
const y = localpos[1] - this.margin
|
||||
const pos: Point = [x, y]
|
||||
const max_dist = 30 / graphcanvas.ds.scale
|
||||
//search closer one
|
||||
// search closer one
|
||||
this.selected = this.getCloserPoint(pos, max_dist)
|
||||
//create one
|
||||
// create one
|
||||
if (this.selected == -1) {
|
||||
const point: Point = [x / w, 1 - y / h]
|
||||
points.push(point)
|
||||
points.sort(function (a, b) { return a[0] - b[0] })
|
||||
points.sort(function (a, b) {
|
||||
return a[0] - b[0]
|
||||
})
|
||||
this.selected = points.indexOf(point)
|
||||
this.must_update = true
|
||||
}
|
||||
if (this.selected != -1)
|
||||
return true
|
||||
if (this.selected != -1) return true
|
||||
}
|
||||
|
||||
onMouseMove(localpos: Point, graphcanvas: LGraphCanvas): void {
|
||||
const points = this.points
|
||||
if (!points)
|
||||
return
|
||||
if (!points) return
|
||||
|
||||
const s = this.selected
|
||||
if (s < 0)
|
||||
return
|
||||
if (s < 0) return
|
||||
|
||||
const x = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2)
|
||||
const y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2)
|
||||
const curvepos: Point = [(localpos[0] - this.margin), (localpos[1] - this.margin)]
|
||||
const curvepos: Point = [
|
||||
localpos[0] - this.margin,
|
||||
localpos[1] - this.margin,
|
||||
]
|
||||
const max_dist = 30 / graphcanvas.ds.scale
|
||||
this._nearest = this.getCloserPoint(curvepos, max_dist)
|
||||
const point = points[s]
|
||||
if (point) {
|
||||
const is_edge_point = s == 0 || s == points.length - 1
|
||||
if (!is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10)) {
|
||||
if (
|
||||
!is_edge_point &&
|
||||
(localpos[0] < -10 ||
|
||||
localpos[0] > this.size[0] + 10 ||
|
||||
localpos[1] < -10 ||
|
||||
localpos[1] > this.size[1] + 10)
|
||||
) {
|
||||
points.splice(s, 1)
|
||||
this.selected = -1
|
||||
return
|
||||
}
|
||||
if (!is_edge_point) //not edges
|
||||
point[0] = clamp(x, 0, 1)
|
||||
else
|
||||
point[0] = s == 0 ? 0 : 1
|
||||
// not edges
|
||||
if (!is_edge_point) point[0] = clamp(x, 0, 1)
|
||||
else point[0] = s == 0 ? 0 : 1
|
||||
point[1] = 1.0 - clamp(y, 0, 1)
|
||||
points.sort(function (a, b) { return a[0] - b[0] })
|
||||
points.sort(function (a, b) {
|
||||
return a[0] - b[0]
|
||||
})
|
||||
this.selected = points.indexOf(point)
|
||||
this.must_update = true
|
||||
}
|
||||
@@ -149,22 +166,23 @@ export class CurveEditor {
|
||||
|
||||
getCloserPoint(pos: Point, max_dist: number): number {
|
||||
const points = this.points
|
||||
if (!points)
|
||||
return -1
|
||||
if (!points) return -1
|
||||
|
||||
max_dist = max_dist || 30
|
||||
const w = (this.size[0] - this.margin * 2)
|
||||
const h = (this.size[1] - this.margin * 2)
|
||||
const w = this.size[0] - this.margin * 2
|
||||
const h = this.size[1] - this.margin * 2
|
||||
const num = points.length
|
||||
const p2: Point = [0, 0]
|
||||
let min_dist = 1000000
|
||||
let closest = -1
|
||||
|
||||
for (let i = 0; i < num; ++i) {
|
||||
const p = points[i]
|
||||
p2[0] = p[0] * w
|
||||
p2[1] = (1.0 - p[1]) * h
|
||||
const dist = distance(pos, p2)
|
||||
if (dist > min_dist || dist > max_dist)
|
||||
continue
|
||||
if (dist > min_dist || dist > max_dist) continue
|
||||
|
||||
closest = i
|
||||
min_dist = dist
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export class DragAndScale {
|
||||
get offset(): Point {
|
||||
return this.state.offset
|
||||
}
|
||||
|
||||
set offset(value: Point) {
|
||||
this.state.offset = value
|
||||
}
|
||||
@@ -42,6 +43,7 @@ export class DragAndScale {
|
||||
get scale(): number {
|
||||
return this.state.scale
|
||||
}
|
||||
|
||||
set scale(value: number) {
|
||||
this.state.scale = value
|
||||
}
|
||||
@@ -49,7 +51,7 @@ export class DragAndScale {
|
||||
constructor(element?: HTMLCanvasElement, skip_events?: boolean) {
|
||||
this.state = {
|
||||
offset: new Float32Array([0, 0]),
|
||||
scale: 1
|
||||
scale: 1,
|
||||
}
|
||||
this.max_scale = 10
|
||||
this.min_scale = 0.1
|
||||
@@ -77,11 +79,7 @@ export class DragAndScale {
|
||||
LiteGraph.pointerListenerAdd(element, "move", this._binded_mouse_callback)
|
||||
LiteGraph.pointerListenerAdd(element, "up", this._binded_mouse_callback)
|
||||
|
||||
element.addEventListener(
|
||||
"mousewheel",
|
||||
this._binded_mouse_callback,
|
||||
false
|
||||
)
|
||||
element.addEventListener("mousewheel", this._binded_mouse_callback, false)
|
||||
element.addEventListener("wheel", this._binded_mouse_callback, false)
|
||||
}
|
||||
|
||||
@@ -150,10 +148,10 @@ export class DragAndScale {
|
||||
LiteGraph.pointerListenerRemove(document, "move", this._binded_mouse_callback)
|
||||
LiteGraph.pointerListenerRemove(document, "up", this._binded_mouse_callback)
|
||||
LiteGraph.pointerListenerAdd(canvas, "move", this._binded_mouse_callback)
|
||||
} else if (is_inside &&
|
||||
(e.type == "mousewheel" ||
|
||||
e.type == "wheel" ||
|
||||
e.type == "DOMMouseScroll")) {
|
||||
} else if (
|
||||
is_inside &&
|
||||
(e.type == "mousewheel" || e.type == "wheel" || e.type == "DOMMouseScroll")
|
||||
) {
|
||||
// @ts-expect-error Deprecated
|
||||
e.eventType = "mousewheel"
|
||||
// @ts-expect-error Deprecated
|
||||
@@ -161,7 +159,7 @@ export class DragAndScale {
|
||||
// @ts-expect-error Deprecated
|
||||
else e.wheel = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60
|
||||
|
||||
//from stack overflow
|
||||
// from stack overflow
|
||||
// @ts-expect-error Deprecated
|
||||
e.delta = e.wheelDelta
|
||||
// @ts-expect-error Deprecated
|
||||
@@ -191,7 +189,7 @@ export class DragAndScale {
|
||||
convertOffsetToCanvas(pos: Point): Point {
|
||||
return [
|
||||
(pos[0] + this.offset[0]) * this.scale,
|
||||
(pos[1] + this.offset[1]) * this.scale
|
||||
(pos[1] + this.offset[1]) * this.scale,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -223,10 +221,7 @@ export class DragAndScale {
|
||||
const rect = this.element.getBoundingClientRect()
|
||||
if (!rect) return
|
||||
|
||||
zooming_center = zooming_center || [
|
||||
rect.width * 0.5,
|
||||
rect.height * 0.5
|
||||
]
|
||||
zooming_center = zooming_center || [rect.width * 0.5, rect.height * 0.5]
|
||||
const center = this.convertCanvasToOffset(zooming_center)
|
||||
this.scale = value
|
||||
if (Math.abs(this.scale - 1) < 0.01) this.scale = 1
|
||||
@@ -234,7 +229,7 @@ export class DragAndScale {
|
||||
const new_center = this.convertCanvasToOffset(zooming_center)
|
||||
const delta_offset = [
|
||||
new_center[0] - center[0],
|
||||
new_center[1] - center[1]
|
||||
new_center[1] - center[1],
|
||||
]
|
||||
|
||||
this.offset[0] += delta_offset[0]
|
||||
|
||||
347
src/LGraph.ts
347
src/LGraph.ts
File diff suppressed because it is too large
Load Diff
@@ -4,23 +4,23 @@ export enum BadgePosition {
|
||||
}
|
||||
|
||||
export interface LGraphBadgeOptions {
|
||||
text: string;
|
||||
fgColor?: string;
|
||||
bgColor?: string;
|
||||
fontSize?: number;
|
||||
padding?: number;
|
||||
height?: number;
|
||||
cornerRadius?: number;
|
||||
text: string
|
||||
fgColor?: string
|
||||
bgColor?: string
|
||||
fontSize?: number
|
||||
padding?: number
|
||||
height?: number
|
||||
cornerRadius?: number
|
||||
}
|
||||
|
||||
export class LGraphBadge {
|
||||
text: string;
|
||||
fgColor: string;
|
||||
bgColor: string;
|
||||
fontSize: number;
|
||||
padding: number;
|
||||
height: number;
|
||||
cornerRadius: number;
|
||||
text: string
|
||||
fgColor: string
|
||||
bgColor: string
|
||||
fontSize: number
|
||||
padding: number
|
||||
height: number
|
||||
cornerRadius: number
|
||||
|
||||
constructor({
|
||||
text,
|
||||
@@ -31,27 +31,27 @@ export class LGraphBadge {
|
||||
height = 20,
|
||||
cornerRadius = 5,
|
||||
}: LGraphBadgeOptions) {
|
||||
this.text = text;
|
||||
this.fgColor = fgColor;
|
||||
this.bgColor = bgColor;
|
||||
this.fontSize = fontSize;
|
||||
this.padding = padding;
|
||||
this.height = height;
|
||||
this.cornerRadius = cornerRadius;
|
||||
this.text = text
|
||||
this.fgColor = fgColor
|
||||
this.bgColor = bgColor
|
||||
this.fontSize = fontSize
|
||||
this.padding = padding
|
||||
this.height = height
|
||||
this.cornerRadius = cornerRadius
|
||||
}
|
||||
|
||||
get visible() {
|
||||
return this.text.length > 0;
|
||||
return this.text.length > 0
|
||||
}
|
||||
|
||||
getWidth(ctx: CanvasRenderingContext2D) {
|
||||
if (!this.visible) return 0;
|
||||
if (!this.visible) return 0
|
||||
|
||||
ctx.save();
|
||||
ctx.font = `${this.fontSize}px sans-serif`;
|
||||
const textWidth = ctx.measureText(this.text).width;
|
||||
ctx.restore();
|
||||
return textWidth + this.padding * 2;
|
||||
ctx.save()
|
||||
ctx.font = `${this.fontSize}px sans-serif`
|
||||
const textWidth = ctx.measureText(this.text).width
|
||||
ctx.restore()
|
||||
return textWidth + this.padding * 2
|
||||
}
|
||||
|
||||
draw(
|
||||
@@ -59,32 +59,32 @@ export class LGraphBadge {
|
||||
x: number,
|
||||
y: number,
|
||||
): void {
|
||||
if (!this.visible) return;
|
||||
if (!this.visible) return
|
||||
|
||||
ctx.save();
|
||||
ctx.font = `${this.fontSize}px sans-serif`;
|
||||
const badgeWidth = this.getWidth(ctx);
|
||||
const badgeX = 0;
|
||||
ctx.save()
|
||||
ctx.font = `${this.fontSize}px sans-serif`
|
||||
const badgeWidth = this.getWidth(ctx)
|
||||
const badgeX = 0
|
||||
|
||||
// Draw badge background
|
||||
ctx.fillStyle = this.bgColor;
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = this.bgColor
|
||||
ctx.beginPath()
|
||||
if (ctx.roundRect) {
|
||||
ctx.roundRect(x + badgeX, y, badgeWidth, this.height, this.cornerRadius);
|
||||
ctx.roundRect(x + badgeX, y, badgeWidth, this.height, this.cornerRadius)
|
||||
} else {
|
||||
// Fallback for browsers that don't support roundRect
|
||||
ctx.rect(x + badgeX, y, badgeWidth, this.height);
|
||||
ctx.rect(x + badgeX, y, badgeWidth, this.height)
|
||||
}
|
||||
ctx.fill();
|
||||
ctx.fill()
|
||||
|
||||
// Draw badge text
|
||||
ctx.fillStyle = this.fgColor;
|
||||
ctx.fillStyle = this.fgColor
|
||||
ctx.fillText(
|
||||
this.text,
|
||||
x + badgeX + this.padding,
|
||||
y + this.height - this.padding
|
||||
);
|
||||
y + this.height - this.padding,
|
||||
)
|
||||
|
||||
ctx.restore();
|
||||
ctx.restore()
|
||||
}
|
||||
}
|
||||
|
||||
2157
src/LGraphCanvas.ts
2157
src/LGraphCanvas.ts
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,22 @@
|
||||
import type { IContextMenuValue, IPinnable, Point, Positionable, Size } from "./interfaces"
|
||||
import type {
|
||||
IContextMenuValue,
|
||||
IPinnable,
|
||||
Point,
|
||||
Positionable,
|
||||
Size,
|
||||
} from "./interfaces"
|
||||
import type { LGraph } from "./LGraph"
|
||||
import type { ISerialisedGroup } from "./types/serialisation"
|
||||
import { LiteGraph } from "./litegraph"
|
||||
import { LGraphCanvas } from "./LGraphCanvas"
|
||||
import { containsCentre, containsRect, isInRectangle, isPointInRect, createBounds, snapPoint } from "./measure"
|
||||
import {
|
||||
containsCentre,
|
||||
containsRect,
|
||||
isInRectangle,
|
||||
isPointInRect,
|
||||
createBounds,
|
||||
snapPoint,
|
||||
} from "./measure"
|
||||
import { LGraphNode } from "./LGraphNode"
|
||||
import { RenderShape, TitleMode } from "./types/globalEnums"
|
||||
|
||||
@@ -16,14 +29,19 @@ export class LGraphGroup implements Positionable, IPinnable {
|
||||
static minHeight = 80
|
||||
static resizeLength = 10
|
||||
static padding = 4
|
||||
static defaultColour = '#335'
|
||||
static defaultColour = "#335"
|
||||
|
||||
id: number
|
||||
color: string
|
||||
title: string
|
||||
font?: string
|
||||
font_size: number = LiteGraph.DEFAULT_GROUP_FONT || 24
|
||||
_bounding: Float32Array = new Float32Array([10, 10, LGraphGroup.minWidth, LGraphGroup.minHeight])
|
||||
_bounding: Float32Array = new Float32Array([
|
||||
10,
|
||||
10,
|
||||
LGraphGroup.minWidth,
|
||||
LGraphGroup.minHeight,
|
||||
])
|
||||
_pos: Point = this._bounding.subarray(0, 2)
|
||||
_size: Size = this._bounding.subarray(2, 4)
|
||||
/** @deprecated See {@link _children} */
|
||||
@@ -46,6 +64,7 @@ export class LGraphGroup implements Positionable, IPinnable {
|
||||
get pos() {
|
||||
return this._pos
|
||||
}
|
||||
|
||||
set pos(v) {
|
||||
if (!v || v.length < 2) return
|
||||
|
||||
@@ -57,6 +76,7 @@ export class LGraphGroup implements Positionable, IPinnable {
|
||||
get size() {
|
||||
return this._size
|
||||
}
|
||||
|
||||
set size(v) {
|
||||
if (!v || v.length < 2) return
|
||||
|
||||
@@ -273,16 +293,16 @@ export class LGraphGroup implements Positionable, IPinnable {
|
||||
{
|
||||
content: "Color",
|
||||
has_submenu: true,
|
||||
callback: LGraphCanvas.onMenuNodeColors
|
||||
callback: LGraphCanvas.onMenuNodeColors,
|
||||
},
|
||||
{
|
||||
content: "Font size",
|
||||
property: "font_size",
|
||||
type: "Number",
|
||||
callback: LGraphCanvas.onShowPropertyEditor
|
||||
callback: LGraphCanvas.onShowPropertyEditor,
|
||||
},
|
||||
null,
|
||||
{ content: "Remove", callback: LGraphCanvas.onMenuNodeRemove }
|
||||
{ content: "Remove", callback: LGraphCanvas.onMenuNodeRemove },
|
||||
]
|
||||
}
|
||||
|
||||
@@ -296,9 +316,11 @@ export class LGraphGroup implements Positionable, IPinnable {
|
||||
const right = b[0] + b[2]
|
||||
const bottom = b[1] + b[3]
|
||||
|
||||
return x < right
|
||||
&& y < bottom
|
||||
&& (x - right) + (y - bottom) > -LGraphGroup.resizeLength
|
||||
return (
|
||||
x < right &&
|
||||
y < bottom &&
|
||||
x - right + (y - bottom) > -LGraphGroup.resizeLength
|
||||
)
|
||||
}
|
||||
|
||||
isPointInside = LGraphNode.prototype.isPointInside
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
69
src/LLink.ts
69
src/LLink.ts
@@ -1,13 +1,25 @@
|
||||
import type { CanvasColour, LinkNetwork, ISlotType, LinkSegment } from "./interfaces"
|
||||
import type {
|
||||
CanvasColour,
|
||||
LinkNetwork,
|
||||
ISlotType,
|
||||
LinkSegment,
|
||||
} from "./interfaces"
|
||||
import type { NodeId } from "./LGraphNode"
|
||||
import type { Reroute, RerouteId } from "./Reroute"
|
||||
import type { Serialisable, SerialisableLLink } from "./types/serialisation"
|
||||
|
||||
export type LinkId = number
|
||||
|
||||
export type SerialisedLLinkArray = [id: LinkId, origin_id: NodeId, origin_slot: number, target_id: NodeId, target_slot: number, type: ISlotType]
|
||||
export type SerialisedLLinkArray = [
|
||||
id: LinkId,
|
||||
origin_id: NodeId,
|
||||
origin_slot: number,
|
||||
target_id: NodeId,
|
||||
target_slot: number,
|
||||
type: ISlotType,
|
||||
]
|
||||
|
||||
//this is the class in charge of storing link information
|
||||
// this is the class in charge of storing link information
|
||||
export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
/** Link ID */
|
||||
id: LinkId
|
||||
@@ -35,12 +47,23 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
|
||||
#color?: CanvasColour
|
||||
/** Custom colour for this link only */
|
||||
public get color(): CanvasColour { return this.#color }
|
||||
public get color(): CanvasColour {
|
||||
return this.#color
|
||||
}
|
||||
|
||||
public set color(value: CanvasColour) {
|
||||
this.#color = value === "" ? null : value
|
||||
}
|
||||
|
||||
constructor(id: LinkId, type: ISlotType, origin_id: NodeId, origin_slot: number, target_id: NodeId, target_slot: number, parentId?: RerouteId) {
|
||||
constructor(
|
||||
id: LinkId,
|
||||
type: ISlotType,
|
||||
origin_id: NodeId,
|
||||
origin_slot: number,
|
||||
target_id: NodeId,
|
||||
target_slot: number,
|
||||
parentId?: RerouteId,
|
||||
) {
|
||||
this.id = id
|
||||
this.type = type
|
||||
this.origin_id = origin_id
|
||||
@@ -50,7 +73,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
this.parentId = parentId
|
||||
|
||||
this._data = null
|
||||
this._pos = new Float32Array(2) //center
|
||||
this._pos = new Float32Array(2) // center
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link LLink.create} */
|
||||
@@ -64,14 +87,26 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
* @returns A new LLink
|
||||
*/
|
||||
static create(data: SerialisableLLink): LLink {
|
||||
return new LLink(data.id, data.type, data.origin_id, data.origin_slot, data.target_id, data.target_slot, data.parentId)
|
||||
return new LLink(
|
||||
data.id,
|
||||
data.type,
|
||||
data.origin_id,
|
||||
data.origin_slot,
|
||||
data.target_id,
|
||||
data.target_slot,
|
||||
data.parentId,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all reroutes from the output slot to this segment. If this segment is a reroute, it will be the last element.
|
||||
* @returns An ordered array of all reroutes from the node output to this reroute or the reroute before it. Otherwise, an empty array.
|
||||
* @returns An ordered array of all reroutes from the node output to
|
||||
* this reroute or the reroute before it. Otherwise, an empty array.
|
||||
*/
|
||||
static getReroutes(network: LinkNetwork, linkSegment: LinkSegment): Reroute[] {
|
||||
static getReroutes(
|
||||
network: LinkNetwork,
|
||||
linkSegment: LinkSegment,
|
||||
): Reroute[] {
|
||||
return network.reroutes.get(linkSegment.parentId)
|
||||
?.getReroutes() ?? []
|
||||
}
|
||||
@@ -79,11 +114,16 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
/**
|
||||
* Finds the reroute in the chain after the provided reroute ID.
|
||||
* @param network The network this link belongs to
|
||||
* @param linkSegment The starting point of the search (input side). Typically the LLink object itself, but can be any link segment.
|
||||
* @param linkSegment The starting point of the search (input side).
|
||||
* Typically the LLink object itself, but can be any link segment.
|
||||
* @param rerouteId The matching reroute will have this set as its {@link parentId}.
|
||||
* @returns The reroute that was found, `undefined` if no reroute was found, or `null` if an infinite loop was detected.
|
||||
*/
|
||||
static findNextReroute(network: LinkNetwork, linkSegment: LinkSegment, rerouteId: RerouteId): Reroute | null | undefined {
|
||||
static findNextReroute(
|
||||
network: LinkNetwork,
|
||||
linkSegment: LinkSegment,
|
||||
rerouteId: RerouteId,
|
||||
): Reroute | null | undefined {
|
||||
return network.reroutes.get(linkSegment.parentId)
|
||||
?.findNextReroute(rerouteId)
|
||||
}
|
||||
@@ -117,7 +157,8 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
|
||||
for (const reroute of reroutes) {
|
||||
reroute.linkIds.delete(this.id)
|
||||
if (!keepReroutes && !reroute.linkIds.size) network.reroutes.delete(reroute.id)
|
||||
if (!keepReroutes && !reroute.linkIds.size)
|
||||
network.reroutes.delete(reroute.id)
|
||||
}
|
||||
network.links.delete(this.id)
|
||||
}
|
||||
@@ -133,7 +174,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
this.origin_slot,
|
||||
this.target_id,
|
||||
this.target_slot,
|
||||
this.type
|
||||
this.type,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -144,7 +185,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
origin_slot: this.origin_slot,
|
||||
target_id: this.target_id,
|
||||
target_slot: this.target_slot,
|
||||
type: this.type
|
||||
type: this.type,
|
||||
}
|
||||
if (this.parentId) copy.parentId = this.parentId
|
||||
return copy
|
||||
|
||||
@@ -5,7 +5,14 @@ import { DragAndScale } from "./DragAndScale"
|
||||
import { LGraphCanvas } from "./LGraphCanvas"
|
||||
import { ContextMenu } from "./ContextMenu"
|
||||
import { CurveEditor } from "./CurveEditor"
|
||||
import { LGraphEventMode, LinkDirection, LinkRenderType, NodeSlotType, RenderShape, TitleMode } from "./types/globalEnums"
|
||||
import {
|
||||
LGraphEventMode,
|
||||
LinkDirection,
|
||||
LinkRenderType,
|
||||
NodeSlotType,
|
||||
RenderShape,
|
||||
TitleMode,
|
||||
} from "./types/globalEnums"
|
||||
import { LGraphNode } from "./LGraphNode"
|
||||
import { SlotShape, SlotDirection, SlotType, LabelPosition } from "./draw"
|
||||
import type { Dictionary, ISlotType, Rect } from "./interfaces"
|
||||
@@ -61,11 +68,11 @@ export class LiteGraphGlobal {
|
||||
EVENT_LINK_COLOR = "#A86"
|
||||
CONNECTING_LINK_COLOR = "#AFA"
|
||||
|
||||
MAX_NUMBER_OF_NODES = 10000 //avoid infinite loops
|
||||
DEFAULT_POSITION = [100, 100] //default node position
|
||||
VALID_SHAPES = ["default", "box", "round", "card"] //,"circle"
|
||||
MAX_NUMBER_OF_NODES = 10000 // avoid infinite loops
|
||||
DEFAULT_POSITION = [100, 100] // default node position
|
||||
VALID_SHAPES = ["default", "box", "round", "card"] // ,"circle"
|
||||
|
||||
//shapes are used for nodes but also for slots
|
||||
// shapes are used for nodes but also for slots
|
||||
BOX_SHAPE = RenderShape.BOX
|
||||
ROUND_SHAPE = RenderShape.ROUND
|
||||
CIRCLE_SHAPE = RenderShape.CIRCLE
|
||||
@@ -73,13 +80,13 @@ export class LiteGraphGlobal {
|
||||
ARROW_SHAPE = RenderShape.ARROW
|
||||
GRID_SHAPE = RenderShape.GRID // intended for slot arrays
|
||||
|
||||
//enums
|
||||
// enums
|
||||
INPUT = NodeSlotType.INPUT
|
||||
OUTPUT = NodeSlotType.OUTPUT
|
||||
|
||||
// TODO: -1 can lead to ambiguity in JS; these should be updated to a more explicit constant or Symbol.
|
||||
EVENT = -1 as const //for outputs
|
||||
ACTION = -1 as const //for inputs
|
||||
EVENT = -1 as const // for outputs
|
||||
ACTION = -1 as const // for inputs
|
||||
|
||||
NODE_MODES = ["Always", "On Event", "Never", "On Trigger"] // helper, will add "On Request" and more in the future
|
||||
NODE_MODES_COLORS = ["#666", "#422", "#333", "#224", "#626"] // use with node_box_coloured_by_mode
|
||||
@@ -107,19 +114,19 @@ export class LiteGraphGlobal {
|
||||
|
||||
VERTICAL_LAYOUT = "vertical" // arrange nodes vertically
|
||||
|
||||
proxy = null //used to redirect calls
|
||||
proxy = null // used to redirect calls
|
||||
node_images_path = ""
|
||||
|
||||
debug = false
|
||||
catch_exceptions = true
|
||||
throw_errors = true
|
||||
allow_scripts = false //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits
|
||||
registered_node_types: Record<string, typeof LGraphNode> = {} //nodetypes by string
|
||||
node_types_by_file_extension = {} //used for dropping files in the canvas
|
||||
Nodes: Record<string, typeof LGraphNode> = {} //node types by classname
|
||||
Globals = {} //used to store vars between graphs
|
||||
allow_scripts = false // if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits
|
||||
registered_node_types: Record<string, typeof LGraphNode> = {} // nodetypes by string
|
||||
node_types_by_file_extension = {} // used for dropping files in the canvas
|
||||
Nodes: Record<string, typeof LGraphNode> = {} // node types by classname
|
||||
Globals = {} // used to store vars between graphs
|
||||
|
||||
searchbox_extras = {} //used to add extra features to the search box
|
||||
searchbox_extras = {} // used to add extra features to the search box
|
||||
auto_sort_node_types = false // [true!] If set to true, will automatically sort node types / categories in the context menus
|
||||
|
||||
node_box_coloured_when_on = false // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback
|
||||
@@ -154,14 +161,14 @@ export class LiteGraphGlobal {
|
||||
|
||||
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)
|
||||
middle_click_slot_add_default_node = false // [true!] allows to create and connect a ndoe clicking with the third button (wheel)
|
||||
|
||||
release_link_on_empty_shows_menu = false //[true!] dragging a link to empty space will open a menu, add from list, search or defaults
|
||||
release_link_on_empty_shows_menu = false // [true!] dragging a link to empty space will open a menu, add from list, search or defaults
|
||||
|
||||
pointerevents_method = "pointer" // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now)
|
||||
|
||||
// TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary)
|
||||
ctrl_shift_v_paste_connect_unselected_outputs = true //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes
|
||||
ctrl_shift_v_paste_connect_unselected_outputs = true // [true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes
|
||||
|
||||
// if true, all newly created nodes/links will use string UUIDs for their id fields instead of integers.
|
||||
// use this if you must have node IDs that are unique across all graphs and subgraphs.
|
||||
@@ -187,13 +194,13 @@ export class LiteGraphGlobal {
|
||||
static {
|
||||
LGraphCanvas.link_type_colors = {
|
||||
"-1": LiteGraphGlobal.DEFAULT_EVENT_LINK_COLOR,
|
||||
number: "#AAA",
|
||||
node: "#DCA"
|
||||
"number": "#AAA",
|
||||
"node": "#DCA",
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
//timer that works everywhere
|
||||
// timer that works everywhere
|
||||
if (typeof performance != "undefined") {
|
||||
this.getTime = performance.now.bind(performance)
|
||||
} else if (typeof Date != "undefined" && Date.now) {
|
||||
@@ -229,7 +236,7 @@ export class LiteGraphGlobal {
|
||||
|
||||
base_class.title ||= classname
|
||||
|
||||
//extend class
|
||||
// extend class
|
||||
for (const i in LGraphNode.prototype) {
|
||||
base_class.prototype[i] ||= LGraphNode.prototype[i]
|
||||
}
|
||||
@@ -265,10 +272,10 @@ export class LiteGraphGlobal {
|
||||
return this._shape
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
configurable: true,
|
||||
})
|
||||
|
||||
//used to know which nodes to create when dragging files to the canvas
|
||||
// used to know which nodes to create when dragging files to the canvas
|
||||
if (base_class.supported_extensions) {
|
||||
for (const i in base_class.supported_extensions) {
|
||||
const ext = base_class.supported_extensions[i]
|
||||
@@ -285,7 +292,7 @@ export class LiteGraphGlobal {
|
||||
this.onNodeTypeRegistered?.(type, base_class)
|
||||
if (prev) this.onNodeTypeReplaced?.(type, base_class, prev)
|
||||
|
||||
//warnings
|
||||
// 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`)
|
||||
|
||||
@@ -314,7 +321,11 @@ export class LiteGraphGlobal {
|
||||
* @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(type: LGraphNode, slot_type: ISlotType, out?: boolean): void {
|
||||
registerNodeAndSlotType(
|
||||
type: LGraphNode,
|
||||
slot_type: ISlotType,
|
||||
out?: boolean,
|
||||
): void {
|
||||
out ||= false
|
||||
// @ts-expect-error Confirm this function no longer supports string types - base_class should always be an instance not a constructor.
|
||||
const base_class = typeof type === "string" && this.registered_node_types[type] !== "anonymous"
|
||||
@@ -357,11 +368,13 @@ export class LiteGraphGlobal {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new nodetype by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function.
|
||||
* Create a new nodetype by passing a function, it wraps it with a proper class and
|
||||
* generates inputs according to the parameters of the function.
|
||||
* Useful to wrap simple methods that do not require properties, and that only process some input to generate an output.
|
||||
* @param {String} name node name with namespace (p.e.: 'math/sum')
|
||||
* @param {Function} func
|
||||
* @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type
|
||||
* @param {Array} param_types [optional] an array containing the type of every parameter,
|
||||
* otherwise parameters will accept any type
|
||||
* @param {String} return_type [optional] string with the return type, otherwise it will be generic
|
||||
* @param {Object} properties [optional] properties to be configurable
|
||||
*/
|
||||
@@ -370,7 +383,7 @@ export class LiteGraphGlobal {
|
||||
func: (...args: any) => any,
|
||||
param_types: string[],
|
||||
return_type: string,
|
||||
properties: unknown
|
||||
properties: unknown,
|
||||
) {
|
||||
const params = Array(func.length)
|
||||
let code = ""
|
||||
@@ -417,7 +430,7 @@ export class LiteGraphGlobal {
|
||||
LGraphNode.prototype[name] = func
|
||||
for (const i in this.registered_node_types) {
|
||||
const type = this.registered_node_types[i]
|
||||
//keep old in case of replacing
|
||||
// keep old in case of replacing
|
||||
if (type.prototype[name]) type.prototype["_" + name] = type.prototype[name]
|
||||
type.prototype[name] = func
|
||||
}
|
||||
@@ -429,7 +442,11 @@ export class LiteGraphGlobal {
|
||||
* @param {String} name a name to distinguish from other nodes
|
||||
* @param {Object} options to set options
|
||||
*/
|
||||
createNode(type: string, title?: string, options?: Dictionary<unknown>): LGraphNode {
|
||||
createNode(
|
||||
type: string,
|
||||
title?: string,
|
||||
options?: Dictionary<unknown>,
|
||||
): LGraphNode {
|
||||
const base_class = this.registered_node_types[type]
|
||||
if (!base_class) {
|
||||
if (this.debug) console.log(`GraphNode type "${type}" not registered.`)
|
||||
@@ -457,12 +474,12 @@ export class LiteGraphGlobal {
|
||||
node.properties ||= {}
|
||||
node.properties_info ||= []
|
||||
node.flags ||= {}
|
||||
//call onresize?
|
||||
// call onresize?
|
||||
node.size ||= node.computeSize()
|
||||
node.pos ||= this.DEFAULT_POSITION.concat()
|
||||
node.mode ||= LGraphEventMode.ALWAYS
|
||||
|
||||
//extra options
|
||||
// extra options
|
||||
if (options) {
|
||||
for (const i in options) {
|
||||
node[i] = options[i]
|
||||
@@ -520,8 +537,8 @@ export class LiteGraphGlobal {
|
||||
for (const i in this.registered_node_types) {
|
||||
const type = this.registered_node_types[i]
|
||||
if (type.category && !type.skip_list) {
|
||||
if (type.filter != filter)
|
||||
continue
|
||||
if (type.filter != filter) continue
|
||||
|
||||
categories[type.category] = 1
|
||||
}
|
||||
}
|
||||
@@ -532,10 +549,10 @@ export class LiteGraphGlobal {
|
||||
return this.auto_sort_node_types ? result.sort() : result
|
||||
}
|
||||
|
||||
//debug purposes: reloads all the js scripts that matches a wildcard
|
||||
// debug purposes: reloads all the js scripts that matches a wildcard
|
||||
reloadNodes(folder_wildcard: string): void {
|
||||
const tmp = document.getElementsByTagName("script")
|
||||
//weird, this array changes by its own, so we use a copy
|
||||
// weird, this array changes by its own, so we use a copy
|
||||
const script_files = []
|
||||
for (let i = 0; i < tmp.length; i++) {
|
||||
script_files.push(tmp[i])
|
||||
@@ -565,7 +582,7 @@ export class LiteGraphGlobal {
|
||||
if (this.debug) console.log("Nodes reloaded")
|
||||
}
|
||||
|
||||
//separated just to improve if it doesn't work
|
||||
// separated just to improve if it doesn't work
|
||||
cloneObject<T extends object>(obj: T, target?: T): T {
|
||||
if (obj == null) return null
|
||||
|
||||
@@ -583,7 +600,8 @@ export class LiteGraphGlobal {
|
||||
*/
|
||||
uuidv4(): string {
|
||||
// @ts-ignore
|
||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, a => (a ^ Math.random() * 16 >> a / 4).toString(16))
|
||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, a =>
|
||||
(a ^ ((Math.random() * 16) >> (a / 4))).toString(16))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -596,7 +614,12 @@ export class LiteGraphGlobal {
|
||||
if (type_a == "" || type_a === "*") type_a = 0
|
||||
if (type_b == "" || type_b === "*") type_b = 0
|
||||
// If generic in/output, matching types (valid for triggers), or event/action types
|
||||
if (!type_a || !type_b || type_a == type_b || (type_a == this.EVENT && type_b == this.ACTION))
|
||||
if (
|
||||
!type_a ||
|
||||
!type_b ||
|
||||
type_a == type_b ||
|
||||
(type_a == this.EVENT && type_b == this.ACTION)
|
||||
)
|
||||
return true
|
||||
|
||||
// Enforce string type to handle toLowerCase call (-1 number not ok)
|
||||
@@ -633,7 +656,7 @@ export class LiteGraphGlobal {
|
||||
this.searchbox_extras[description.toLowerCase()] = {
|
||||
type: node_type,
|
||||
desc: description,
|
||||
data: data
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,7 +668,12 @@ export class LiteGraphGlobal {
|
||||
* @param {Function} on_error in case of an error
|
||||
* @return {FileReader|Promise} returns the object used to
|
||||
*/
|
||||
fetchFile(url: string | URL | Request | Blob, type: string, on_complete: (data: string | ArrayBuffer) => void, on_error: (error: unknown) => void): void | Promise<void> {
|
||||
fetchFile(
|
||||
url: string | URL | Request | Blob,
|
||||
type: string,
|
||||
on_complete: (data: string | ArrayBuffer) => void,
|
||||
on_error: (error: unknown) => void,
|
||||
): void | Promise<void> {
|
||||
if (!url) return null
|
||||
|
||||
type = type || "text"
|
||||
@@ -656,7 +684,7 @@ export class LiteGraphGlobal {
|
||||
return fetch(url)
|
||||
.then(function (response) {
|
||||
if (!response.ok)
|
||||
throw new Error("File not found") //it will be catch below
|
||||
throw new Error("File not found") // it will be catch below
|
||||
if (type == "arraybuffer")
|
||||
return response.arrayBuffer()
|
||||
else if (type == "text" || type == "string")
|
||||
@@ -692,7 +720,7 @@ export class LiteGraphGlobal {
|
||||
return null
|
||||
}
|
||||
|
||||
//used to create nodes from wrapping functions
|
||||
// used to create nodes from wrapping functions
|
||||
getParameterNames(func: (...args: any) => any): string[] {
|
||||
return (func + "")
|
||||
.replace(/[/][/].*$/gm, "") // strip single-line comments
|
||||
@@ -706,7 +734,7 @@ export class LiteGraphGlobal {
|
||||
}
|
||||
|
||||
/* helper for interaction: pointer, touch, mouse Listeners
|
||||
used by LGraphCanvas DragAndScale ContextMenu*/
|
||||
used by LGraphCanvas DragAndScale ContextMenu */
|
||||
pointerListenerAdd(oDOM: Node, sEvIn: string, fCall: (e: Event) => boolean | void, capture = false): void {
|
||||
if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall !== "function") return
|
||||
|
||||
@@ -726,7 +754,7 @@ export class LiteGraphGlobal {
|
||||
}
|
||||
case "move": {
|
||||
sMethod = "touch"
|
||||
//sEvent = "move";
|
||||
// sEvent = "move";
|
||||
break
|
||||
}
|
||||
case "up": {
|
||||
@@ -736,7 +764,7 @@ export class LiteGraphGlobal {
|
||||
}
|
||||
case "cancel": {
|
||||
sMethod = "touch"
|
||||
//sEvent = "cancel";
|
||||
// sEvent = "cancel";
|
||||
break
|
||||
}
|
||||
case "enter": {
|
||||
@@ -752,7 +780,7 @@ export class LiteGraphGlobal {
|
||||
|
||||
switch (sEvent) {
|
||||
// @ts-expect-error
|
||||
//both pointer and move events
|
||||
// both pointer and move events
|
||||
case "down": case "up": case "move": case "over": case "out": case "enter":
|
||||
{
|
||||
oDOM.addEventListener(sMethod + sEvent, fCall, capture)
|
||||
@@ -770,12 +798,13 @@ export class LiteGraphGlobal {
|
||||
return oDOM.addEventListener(sEvent, fCall, capture)
|
||||
}
|
||||
}
|
||||
|
||||
pointerListenerRemove(oDOM: Node, sEvent: string, fCall: (e: Event) => boolean | void, capture = false): void {
|
||||
if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall !== "function") return
|
||||
|
||||
switch (sEvent) {
|
||||
// @ts-expect-error
|
||||
//both pointer and move events
|
||||
// both pointer and move events
|
||||
case "down": case "up": case "move": case "over": case "out": case "enter":
|
||||
{
|
||||
if (this.pointerevents_method == "pointer" || this.pointerevents_method == "mouse") {
|
||||
@@ -823,7 +852,7 @@ export class LiteGraphGlobal {
|
||||
|
||||
isInsideRectangle = isInsideRectangle
|
||||
|
||||
//[minx,miny,maxx,maxy]
|
||||
// [minx,miny,maxx,maxy]
|
||||
growBounding(bounding: Rect, x: number, y: number): void {
|
||||
if (x < bounding[0]) {
|
||||
bounding[0] = x
|
||||
@@ -840,7 +869,7 @@ export class LiteGraphGlobal {
|
||||
|
||||
overlapBounding = overlapBounding
|
||||
|
||||
//point inside bounding box
|
||||
// point inside bounding box
|
||||
isInsideBounding(p: number[], bb: number[][]): boolean {
|
||||
if (
|
||||
p[0] < bb[0][0] ||
|
||||
@@ -853,13 +882,13 @@ export class LiteGraphGlobal {
|
||||
return true
|
||||
}
|
||||
|
||||
//Convert a hex value to its decimal value - the inputted hex must be in the
|
||||
// Convert a hex value to its decimal value - the inputted hex must be in the
|
||||
// format of a hex triplet - the kind we use for HTML colours. The function
|
||||
// will return an array with three values.
|
||||
hex2num(hex: string): number[] {
|
||||
if (hex.charAt(0) == "#") {
|
||||
hex = hex.slice(1)
|
||||
} //Remove the '#' char - if there is one.
|
||||
} // Remove the '#' char - if there is one.
|
||||
hex = hex.toUpperCase()
|
||||
const hex_alphabets = "0123456789ABCDEF"
|
||||
const value = new Array(3)
|
||||
@@ -874,7 +903,7 @@ export class LiteGraphGlobal {
|
||||
return value
|
||||
}
|
||||
|
||||
//Give a array with three values as the argument and the function will return
|
||||
// Give a array with three values as the argument and the function will return
|
||||
// the corresponding hex triplet.
|
||||
num2hex(triplet: number[]): string {
|
||||
const hex_alphabets = "0123456789ABCDEF"
|
||||
@@ -911,35 +940,35 @@ export class LiteGraphGlobal {
|
||||
|
||||
extendClass(target: any, origin: any): void {
|
||||
for (const i in origin) {
|
||||
//copy class properties
|
||||
// copy class properties
|
||||
if (target.hasOwnProperty(i)) continue
|
||||
target[i] = origin[i]
|
||||
}
|
||||
|
||||
if (origin.prototype) {
|
||||
//copy prototype properties
|
||||
// copy prototype properties
|
||||
for (const i in origin.prototype) {
|
||||
//only enumerable
|
||||
// only enumerable
|
||||
if (!origin.prototype.hasOwnProperty(i)) continue
|
||||
|
||||
//avoid overwriting existing ones
|
||||
// avoid overwriting existing ones
|
||||
if (target.prototype.hasOwnProperty(i)) continue
|
||||
|
||||
//copy getters
|
||||
// copy getters
|
||||
if (origin.prototype.__lookupGetter__(i)) {
|
||||
target.prototype.__defineGetter__(
|
||||
i,
|
||||
origin.prototype.__lookupGetter__(i)
|
||||
origin.prototype.__lookupGetter__(i),
|
||||
)
|
||||
} else {
|
||||
target.prototype[i] = origin.prototype[i]
|
||||
}
|
||||
|
||||
//and setters
|
||||
// and setters
|
||||
if (origin.prototype.__lookupSetter__(i)) {
|
||||
target.prototype.__defineSetter__(
|
||||
i,
|
||||
origin.prototype.__lookupSetter__(i)
|
||||
origin.prototype.__lookupSetter__(i),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
/** Temporary workaround until downstream consumers migrate to Map. A brittle wrapper with many flaws, but should be fine for simple maps using int indexes. */
|
||||
/**
|
||||
* Temporary workaround until downstream consumers migrate to Map.
|
||||
* A brittle wrapper with many flaws, but should be fine for simple maps using int indexes.
|
||||
*/
|
||||
export class MapProxyHandler<V> implements ProxyHandler<Map<number | string, V>> {
|
||||
getOwnPropertyDescriptor(target: Map<number | string, V>, p: string | symbol): PropertyDescriptor | undefined {
|
||||
getOwnPropertyDescriptor(
|
||||
target: Map<number | string, V>,
|
||||
p: string | symbol,
|
||||
): PropertyDescriptor | undefined {
|
||||
const value = this.get(target, p)
|
||||
if (value) return {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import type { CanvasColour, LinkSegment, LinkNetwork, Point, Positionable, ReadOnlyRect } from "./interfaces"
|
||||
import type {
|
||||
CanvasColour,
|
||||
LinkSegment,
|
||||
LinkNetwork,
|
||||
Point,
|
||||
Positionable,
|
||||
ReadOnlyRect,
|
||||
} from "./interfaces"
|
||||
import { LLink, type LinkId } from "./LLink"
|
||||
import type { SerialisableReroute, Serialisable } from "./types/serialisation"
|
||||
import { distance } from "./measure"
|
||||
@@ -10,7 +17,8 @@ export type RerouteId = number
|
||||
* Represents an additional point on the graph that a link path will travel through. Used for visual organisation only.
|
||||
*
|
||||
* Requires no disposal or clean up.
|
||||
* Stores only primitive values (IDs) to reference other items in its network, and a `WeakRef` to a {@link LinkNetwork} to resolve them.
|
||||
* Stores only primitive values (IDs) to reference other items in its network,
|
||||
* and a `WeakRef` to a {@link LinkNetwork} to resolve them.
|
||||
*/
|
||||
export class Reroute implements Positionable, LinkSegment, Serialisable<SerialisableReroute> {
|
||||
static radius: number = 10
|
||||
@@ -25,6 +33,7 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
|
||||
public get parentId(): RerouteId {
|
||||
return this.#parentId
|
||||
}
|
||||
|
||||
/** Ignores attempts to create an infinite loop. @inheritdoc */
|
||||
public set parentId(value: RerouteId) {
|
||||
if (value === this.id) return
|
||||
@@ -37,8 +46,10 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
|
||||
get pos(): Point {
|
||||
return this.#pos
|
||||
}
|
||||
|
||||
set pos(value: Point) {
|
||||
if (!(value?.length >= 2)) throw new TypeError("Reroute.pos is an x,y point, and expects an indexable with at least two values.")
|
||||
if (!(value?.length >= 2))
|
||||
throw new TypeError("Reroute.pos is an x,y point, and expects an indexable with at least two values.")
|
||||
this.#pos[0] = value[0]
|
||||
this.#pos[1] = value[1]
|
||||
}
|
||||
@@ -110,7 +121,7 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
|
||||
network: LinkNetwork,
|
||||
pos?: Point,
|
||||
parentId?: RerouteId,
|
||||
linkIds?: Iterable<LinkId>
|
||||
linkIds?: Iterable<LinkId>,
|
||||
) {
|
||||
this.#network = new WeakRef(network)
|
||||
this.update(parentId, pos, linkIds)
|
||||
@@ -120,11 +131,16 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
|
||||
/**
|
||||
* Applies a new parentId to the reroute, and optinoally a new position and linkId.
|
||||
* Primarily used for deserialisation.
|
||||
* @param parentId The ID of the reroute prior to this reroute, or `undefined` if it is the first reroute connected to a nodes output
|
||||
* @param parentId The ID of the reroute prior to this reroute, or
|
||||
* `undefined` if it is the first reroute connected to a nodes output
|
||||
* @param pos The position of this reroute
|
||||
* @param linkIds All link IDs that pass through this reroute
|
||||
*/
|
||||
update(parentId: RerouteId | undefined, pos?: Point, linkIds?: Iterable<LinkId>): void {
|
||||
update(
|
||||
parentId: RerouteId | undefined,
|
||||
pos?: Point,
|
||||
linkIds?: Iterable<LinkId>,
|
||||
): void {
|
||||
this.parentId = parentId
|
||||
if (pos) this.pos = pos
|
||||
if (linkIds) this.linkIds = new Set(linkIds)
|
||||
@@ -145,7 +161,8 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
|
||||
|
||||
/**
|
||||
* Retrieves an ordered array of all reroutes from the node output.
|
||||
* @param visited Internal. A set of reroutes that this function has already visited whilst recursing up the chain.
|
||||
* @param visited Internal. A set of reroutes that this function
|
||||
* has already visited whilst recursing up the chain.
|
||||
* @returns An ordered array of all reroutes from the node output to this reroute, inclusive.
|
||||
* `null` if an infinite loop is detected.
|
||||
* `undefined` if the reroute chain or {@link LinkNetwork} are invalid.
|
||||
@@ -175,12 +192,16 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
|
||||
* @param visited A set of reroutes that have already been visited
|
||||
* @returns The reroute that was found, `undefined` if no reroute was found, or `null` if an infinite loop was detected.
|
||||
*/
|
||||
findNextReroute(withParentId: RerouteId, visited = new Set<Reroute>()): Reroute | null | undefined {
|
||||
findNextReroute(
|
||||
withParentId: RerouteId,
|
||||
visited = new Set<Reroute>(),
|
||||
): Reroute | null | undefined {
|
||||
if (this.#parentId === withParentId) return this
|
||||
if (visited.has(this)) return null
|
||||
visited.add(this)
|
||||
|
||||
return this.#network.deref()
|
||||
return this.#network
|
||||
.deref()
|
||||
?.reroutes.get(this.#parentId)
|
||||
?.findNextReroute(withParentId, visited)
|
||||
}
|
||||
@@ -229,7 +250,10 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
|
||||
|
||||
sum /= angles.length
|
||||
|
||||
const originToReroute = Math.atan2(this.#pos[1] - linkStart[1], this.#pos[0] - linkStart[0])
|
||||
const originToReroute = Math.atan2(
|
||||
this.#pos[1] - linkStart[1],
|
||||
this.#pos[0] - linkStart[0],
|
||||
)
|
||||
let diff = (originToReroute - sum) * 0.5
|
||||
if (Math.abs(diff) > Math.PI * 0.5) diff += Math.PI
|
||||
const dist = Math.min(80, distance(linkStart, this.#pos) * 0.25)
|
||||
@@ -286,7 +310,7 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
|
||||
id: this.id,
|
||||
parentId: this.parentId,
|
||||
pos: [this.pos[0], this.pos[1]],
|
||||
linkIds: [...this.linkIds]
|
||||
linkIds: [...this.linkIds],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
98
src/draw.ts
98
src/draw.ts
@@ -1,4 +1,4 @@
|
||||
import type { Vector2 } from "./litegraph";
|
||||
import type { Vector2 } from "./litegraph"
|
||||
import type { INodeSlot } from "./interfaces"
|
||||
import { LinkDirection, RenderShape } from "./types/globalEnums"
|
||||
|
||||
@@ -42,44 +42,44 @@ export function drawSlot(
|
||||
do_stroke = false,
|
||||
highlight = false,
|
||||
}: {
|
||||
label_color?: string;
|
||||
label_position?: LabelPosition;
|
||||
horizontal?: boolean;
|
||||
low_quality?: boolean;
|
||||
render_text?: boolean;
|
||||
do_stroke?: boolean;
|
||||
highlight?: boolean;
|
||||
} = {}
|
||||
label_color?: string
|
||||
label_position?: LabelPosition
|
||||
horizontal?: boolean
|
||||
low_quality?: boolean
|
||||
render_text?: boolean
|
||||
do_stroke?: boolean
|
||||
highlight?: boolean
|
||||
} = {},
|
||||
) {
|
||||
// Save the current fillStyle and strokeStyle
|
||||
const originalFillStyle = ctx.fillStyle;
|
||||
const originalStrokeStyle = ctx.strokeStyle;
|
||||
const originalLineWidth = ctx.lineWidth;
|
||||
const originalFillStyle = ctx.fillStyle
|
||||
const originalStrokeStyle = ctx.strokeStyle
|
||||
const originalLineWidth = ctx.lineWidth
|
||||
|
||||
const slot_type = slot.type as SlotType;
|
||||
const slot_type = slot.type as SlotType
|
||||
const slot_shape = (
|
||||
slot_type === SlotType.Array ? SlotShape.Grid : slot.shape
|
||||
) as SlotShape;
|
||||
) as SlotShape
|
||||
|
||||
ctx.beginPath();
|
||||
let doStroke = do_stroke;
|
||||
let doFill = true;
|
||||
ctx.beginPath()
|
||||
let doStroke = do_stroke
|
||||
let doFill = true
|
||||
|
||||
if (slot_type === SlotType.Event || slot_shape === SlotShape.Box) {
|
||||
if (horizontal) {
|
||||
ctx.rect(pos[0] - 5 + 0.5, pos[1] - 8 + 0.5, 10, 14);
|
||||
ctx.rect(pos[0] - 5 + 0.5, pos[1] - 8 + 0.5, 10, 14)
|
||||
} else {
|
||||
ctx.rect(pos[0] - 6 + 0.5, pos[1] - 5 + 0.5, 14, 10);
|
||||
ctx.rect(pos[0] - 6 + 0.5, pos[1] - 5 + 0.5, 14, 10)
|
||||
}
|
||||
} else if (slot_shape === SlotShape.Arrow) {
|
||||
ctx.moveTo(pos[0] + 8, pos[1] + 0.5);
|
||||
ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);
|
||||
ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);
|
||||
ctx.closePath();
|
||||
ctx.moveTo(pos[0] + 8, pos[1] + 0.5)
|
||||
ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5)
|
||||
ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5)
|
||||
ctx.closePath()
|
||||
} else if (slot_shape === SlotShape.Grid) {
|
||||
const gridSize = 3;
|
||||
const cellSize = 2;
|
||||
const spacing = 3;
|
||||
const gridSize = 3
|
||||
const cellSize = 2
|
||||
const spacing = 3
|
||||
|
||||
for (let x = 0; x < gridSize; x++) {
|
||||
for (let y = 0; y < gridSize; y++) {
|
||||
@@ -87,59 +87,59 @@ export function drawSlot(
|
||||
pos[0] - 4 + x * spacing,
|
||||
pos[1] - 4 + y * spacing,
|
||||
cellSize,
|
||||
cellSize
|
||||
);
|
||||
cellSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
doStroke = false;
|
||||
doStroke = false
|
||||
} else {
|
||||
// Default rendering for circle, hollow circle.
|
||||
if (low_quality) {
|
||||
ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8);
|
||||
ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8)
|
||||
} else {
|
||||
let radius: number;
|
||||
let radius: number
|
||||
if (slot_shape === SlotShape.HollowCircle) {
|
||||
doFill = false;
|
||||
doStroke = true;
|
||||
ctx.lineWidth = 3;
|
||||
ctx.strokeStyle = ctx.fillStyle;
|
||||
radius = highlight ? 4 : 3;
|
||||
doFill = false
|
||||
doStroke = true
|
||||
ctx.lineWidth = 3
|
||||
ctx.strokeStyle = ctx.fillStyle
|
||||
radius = highlight ? 4 : 3
|
||||
} else {
|
||||
// Normal circle
|
||||
radius = highlight ? 5 : 4;
|
||||
radius = highlight ? 5 : 4
|
||||
}
|
||||
ctx.arc(pos[0], pos[1], radius, 0, Math.PI * 2);
|
||||
ctx.arc(pos[0], pos[1], radius, 0, Math.PI * 2)
|
||||
}
|
||||
}
|
||||
|
||||
if (doFill) ctx.fill();
|
||||
if (!low_quality && doStroke) ctx.stroke();
|
||||
if (doFill) ctx.fill()
|
||||
if (!low_quality && doStroke) ctx.stroke()
|
||||
|
||||
// render slot label
|
||||
if (render_text) {
|
||||
const text = slot.label != null ? slot.label : slot.name;
|
||||
const text = slot.label != null ? slot.label : slot.name
|
||||
if (text) {
|
||||
// TODO: Finish impl. Highlight text on mouseover unless we're connecting links.
|
||||
ctx.fillStyle = label_color;
|
||||
ctx.fillStyle = label_color
|
||||
|
||||
if (label_position === LabelPosition.Right) {
|
||||
if (horizontal || slot.dir == LinkDirection.UP) {
|
||||
ctx.fillText(text, pos[0], pos[1] - 10);
|
||||
ctx.fillText(text, pos[0], pos[1] - 10)
|
||||
} else {
|
||||
ctx.fillText(text, pos[0] + 10, pos[1] + 5);
|
||||
ctx.fillText(text, pos[0] + 10, pos[1] + 5)
|
||||
}
|
||||
} else {
|
||||
if (horizontal || slot.dir == LinkDirection.DOWN) {
|
||||
ctx.fillText(text, pos[0], pos[1] - 8);
|
||||
ctx.fillText(text, pos[0], pos[1] - 8)
|
||||
} else {
|
||||
ctx.fillText(text, pos[0] - 10, pos[1] + 5);
|
||||
ctx.fillText(text, pos[0] - 10, pos[1] + 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the original fillStyle and strokeStyle
|
||||
ctx.fillStyle = originalFillStyle;
|
||||
ctx.strokeStyle = originalStrokeStyle;
|
||||
ctx.lineWidth = originalLineWidth;
|
||||
ctx.fillStyle = originalFillStyle
|
||||
ctx.strokeStyle = originalStrokeStyle
|
||||
ctx.lineWidth = originalLineWidth
|
||||
}
|
||||
|
||||
@@ -94,7 +94,10 @@ export interface LinkSegment {
|
||||
path?: Path2D
|
||||
/** Centre point of the {@link path}. Calculated during render only - can be inaccurate */
|
||||
readonly _pos: Float32Array
|
||||
/** Y-forward along the {@link path} from its centre point, in radians. `undefined` if using circles for link centres. Calculated during render only - can be inaccurate. */
|
||||
/** Y-forward along the {@link path} from its centre point, in radians.
|
||||
* `undefined` if using circles for link centres.
|
||||
* Calculated during render only - can be inaccurate.
|
||||
*/
|
||||
_centreAngle?: number
|
||||
|
||||
/** Output node ID */
|
||||
@@ -133,13 +136,31 @@ export type Rect = ArRect | Float32Array | Float64Array
|
||||
export type Rect32 = Float32Array
|
||||
|
||||
/** A point represented as `[x, y]` co-ordinates that will not be modified */
|
||||
export type ReadOnlyPoint = readonly [x: number, y: number] | ReadOnlyTypedArray<Float32Array> | ReadOnlyTypedArray<Float64Array>
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` that will not be modified */
|
||||
export type ReadOnlyRect = readonly [x: number, y: number, width: number, height: number] | ReadOnlyTypedArray<Float32Array> | ReadOnlyTypedArray<Float64Array>
|
||||
export type ReadOnlyPoint =
|
||||
| readonly [x: number, y: number]
|
||||
| ReadOnlyTypedArray<Float32Array>
|
||||
| ReadOnlyTypedArray<Float64Array>
|
||||
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` that will not be modified */
|
||||
export type ReadOnlyRect =
|
||||
| readonly [x: number, y: number, width: number, height: number]
|
||||
| ReadOnlyTypedArray<Float32Array>
|
||||
| ReadOnlyTypedArray<Float64Array>
|
||||
|
||||
type TypedArrays =
|
||||
| Int8Array
|
||||
| Uint8Array
|
||||
| Uint8ClampedArray
|
||||
| Int16Array
|
||||
| Uint16Array
|
||||
| Int32Array
|
||||
| Uint32Array
|
||||
| Float32Array
|
||||
| Float64Array
|
||||
|
||||
type TypedArrays = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array
|
||||
type TypedBigIntArrays = BigInt64Array | BigUint64Array
|
||||
type ReadOnlyTypedArray<T extends TypedArrays | TypedBigIntArrays> = Omit<T, "fill" | "copyWithin" | "reverse" | "set" | "sort" | "subarray">
|
||||
type ReadOnlyTypedArray<T extends TypedArrays | TypedBigIntArrays> =
|
||||
Omit<T, "fill" | "copyWithin" | "reverse" | "set" | "sort" | "subarray">
|
||||
|
||||
/** Union of property names that are of type Match */
|
||||
export type KeysOfType<T, Match> = { [P in keyof T]: T[P] extends Match ? P : never }[keyof T]
|
||||
@@ -216,7 +237,13 @@ export interface ConnectingLink extends IInputOrOutput {
|
||||
interface IContextMenuBase {
|
||||
title?: string
|
||||
className?: string
|
||||
callback?(value?: unknown, options?: unknown, event?: MouseEvent, previous_menu?: ContextMenu, node?: LGraphNode): void | boolean
|
||||
callback?(
|
||||
value?: unknown,
|
||||
options?: unknown,
|
||||
event?: MouseEvent,
|
||||
previous_menu?: ContextMenu,
|
||||
node?: LGraphNode,
|
||||
): void | boolean
|
||||
}
|
||||
|
||||
/** ContextMenu */
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
import type { Point, ConnectingLink } from "./interfaces"
|
||||
import type { INodeSlot, INodeInputSlot, INodeOutputSlot, CanvasColour, Direction, IBoundaryNodes, IContextMenuOptions, IContextMenuValue, IFoundSlot, IInputOrOutput, INodeFlags, IOptionalSlotData, ISlotType, KeysOfType, MethodNames, PickByType, Rect, Rect32, Size } from "./interfaces"
|
||||
import type {
|
||||
INodeSlot,
|
||||
INodeInputSlot,
|
||||
INodeOutputSlot,
|
||||
CanvasColour,
|
||||
Direction,
|
||||
IBoundaryNodes,
|
||||
IContextMenuOptions,
|
||||
IContextMenuValue,
|
||||
IFoundSlot,
|
||||
IInputOrOutput,
|
||||
INodeFlags,
|
||||
IOptionalSlotData,
|
||||
ISlotType,
|
||||
KeysOfType,
|
||||
MethodNames,
|
||||
PickByType,
|
||||
Rect,
|
||||
Rect32,
|
||||
Size,
|
||||
} from "./interfaces"
|
||||
import type { SlotShape, LabelPosition, SlotDirection, SlotType } from "./draw"
|
||||
import type { IWidget } from "./types/widgets"
|
||||
import type { RenderShape, TitleMode } from "./types/globalEnums"
|
||||
@@ -18,19 +38,53 @@ import { CurveEditor } from "./CurveEditor"
|
||||
import { LGraphBadge, BadgePosition } from "./LGraphBadge"
|
||||
|
||||
export const LiteGraph = new LiteGraphGlobal()
|
||||
export { LGraph, LGraphCanvas, LGraphCanvasState, DragAndScale, LGraphNode, LGraphGroup, LLink, ContextMenu, CurveEditor }
|
||||
export { INodeSlot, INodeInputSlot, INodeOutputSlot, ConnectingLink, CanvasColour, Direction, IBoundaryNodes, IContextMenuOptions, IContextMenuValue, IFoundSlot, IInputOrOutput, INodeFlags, IOptionalSlotData, ISlotType, KeysOfType, MethodNames, PickByType, Rect, Rect32, Size }
|
||||
export {
|
||||
LGraph,
|
||||
LGraphCanvas,
|
||||
LGraphCanvasState,
|
||||
DragAndScale,
|
||||
LGraphNode,
|
||||
LGraphGroup,
|
||||
LLink,
|
||||
ContextMenu,
|
||||
CurveEditor,
|
||||
}
|
||||
export {
|
||||
INodeSlot,
|
||||
INodeInputSlot,
|
||||
INodeOutputSlot,
|
||||
ConnectingLink,
|
||||
CanvasColour,
|
||||
Direction,
|
||||
IBoundaryNodes,
|
||||
IContextMenuOptions,
|
||||
IContextMenuValue,
|
||||
IFoundSlot,
|
||||
IInputOrOutput,
|
||||
INodeFlags,
|
||||
IOptionalSlotData,
|
||||
ISlotType,
|
||||
KeysOfType,
|
||||
MethodNames,
|
||||
PickByType,
|
||||
Rect,
|
||||
Rect32,
|
||||
Size,
|
||||
}
|
||||
export { IWidget }
|
||||
export { LGraphBadge, BadgePosition }
|
||||
export { SlotShape, LabelPosition, SlotDirection, SlotType }
|
||||
export { EaseFunction, LinkMarkerShape } from "./types/globalEnums"
|
||||
export type { SerialisableGraph, SerialisableLLink } from "./types/serialisation"
|
||||
export type {
|
||||
SerialisableGraph,
|
||||
SerialisableLLink,
|
||||
} from "./types/serialisation"
|
||||
export { CanvasPointer } from "./CanvasPointer"
|
||||
export { createBounds } from "./measure"
|
||||
|
||||
export function clamp(v: number, a: number, b: number): number {
|
||||
return a > v ? a : b < v ? b : v
|
||||
};
|
||||
}
|
||||
|
||||
// Load legacy polyfills
|
||||
loadPolyfills()
|
||||
@@ -61,7 +115,7 @@ export type ContextMenuEventListener = (
|
||||
options: IContextMenuOptions,
|
||||
event: MouseEvent,
|
||||
parentMenu: ContextMenu | undefined,
|
||||
node: LGraphNode
|
||||
node: LGraphNode,
|
||||
) => boolean | void
|
||||
|
||||
export interface LinkReleaseContext {
|
||||
@@ -77,15 +131,19 @@ export interface LinkReleaseContextExtended {
|
||||
}
|
||||
|
||||
/** @deprecated Confirm no downstream consumers, then remove. */
|
||||
export type LiteGraphCanvasEventType = "empty-release" | "empty-double-click" | "group-double-click"
|
||||
export type LiteGraphCanvasEventType =
|
||||
| "empty-release"
|
||||
| "empty-double-click"
|
||||
| "group-double-click"
|
||||
|
||||
export interface LiteGraphCanvasEvent extends CustomEvent<CanvasEventDetail> { }
|
||||
export interface LiteGraphCanvasEvent extends CustomEvent<CanvasEventDetail> {}
|
||||
|
||||
export interface LiteGraphCanvasGroupEvent extends CustomEvent<{
|
||||
export interface LiteGraphCanvasGroupEvent
|
||||
extends CustomEvent<{
|
||||
subType: "group-double-click"
|
||||
originalEvent: MouseEvent
|
||||
group: LGraphGroup
|
||||
}> { }
|
||||
}> {}
|
||||
|
||||
/** https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#lgraphnode */
|
||||
|
||||
@@ -105,7 +163,7 @@ export interface LGraphNodeConstructor<T extends LGraphNode = LGraphNode> {
|
||||
title_text_color?: string
|
||||
keepAllLinksOnBypass: boolean
|
||||
nodeData: any
|
||||
new(): T
|
||||
new (): T
|
||||
}
|
||||
|
||||
// End backwards compat
|
||||
|
||||
125
src/measure.ts
125
src/measure.ts
@@ -1,4 +1,10 @@
|
||||
import type { Point, Positionable, ReadOnlyPoint, ReadOnlyRect, Rect } from "./interfaces"
|
||||
import type {
|
||||
Point,
|
||||
Positionable,
|
||||
ReadOnlyPoint,
|
||||
ReadOnlyRect,
|
||||
Rect,
|
||||
} from "./interfaces"
|
||||
import { LinkDirection } from "./types/globalEnums"
|
||||
|
||||
/**
|
||||
@@ -9,7 +15,7 @@ import { LinkDirection } from "./types/globalEnums"
|
||||
*/
|
||||
export function distance(a: ReadOnlyPoint, b: ReadOnlyPoint): number {
|
||||
return Math.sqrt(
|
||||
(b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])
|
||||
(b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -38,11 +44,18 @@ export function dist2(x1: number, y1: number, x2: number, y2: number): number {
|
||||
* @param height Rect height
|
||||
* @returns `true` if the point is inside the rect, otherwise `false`
|
||||
*/
|
||||
export function isInRectangle(x: number, y: number, left: number, top: number, width: number, height: number): boolean {
|
||||
return x >= left
|
||||
&& x < left + width
|
||||
&& y >= top
|
||||
&& y < top + height
|
||||
export function isInRectangle(
|
||||
x: number,
|
||||
y: number,
|
||||
left: number,
|
||||
top: number,
|
||||
width: number,
|
||||
height: number,
|
||||
): boolean {
|
||||
return x >= left &&
|
||||
x < left + width &&
|
||||
y >= top &&
|
||||
y < top + height
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,10 +65,10 @@ export function isInRectangle(x: number, y: number, left: number, top: number, w
|
||||
* @returns `true` if the point is inside the rect, otherwise `false`
|
||||
*/
|
||||
export function isPointInRect(point: ReadOnlyPoint, rect: ReadOnlyRect): boolean {
|
||||
return point[0] >= rect[0]
|
||||
&& point[0] < rect[0] + rect[2]
|
||||
&& point[1] >= rect[1]
|
||||
&& point[1] < rect[1] + rect[3]
|
||||
return point[0] >= rect[0] &&
|
||||
point[0] < rect[0] + rect[2] &&
|
||||
point[1] >= rect[1] &&
|
||||
point[1] < rect[1] + rect[3]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,10 +79,10 @@ export function isPointInRect(point: ReadOnlyPoint, rect: ReadOnlyRect): boolean
|
||||
* @returns `true` if the point is inside the rect, otherwise `false`
|
||||
*/
|
||||
export function isInRect(x: number, y: number, rect: ReadOnlyRect): boolean {
|
||||
return x >= rect[0]
|
||||
&& x < rect[0] + rect[2]
|
||||
&& y >= rect[1]
|
||||
&& y < rect[1] + rect[3]
|
||||
return x >= rect[0] &&
|
||||
x < rect[0] + rect[2] &&
|
||||
y >= rect[1] &&
|
||||
y < rect[1] + rect[3]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,11 +101,18 @@ export function isInRect(x: number, y: number, rect: ReadOnlyRect): boolean {
|
||||
* @param height Rect height
|
||||
* @returns `true` if the point is inside the rect, otherwise `false`
|
||||
*/
|
||||
export function isInsideRectangle(x: number, y: number, left: number, top: number, width: number, height: number): boolean {
|
||||
return left < x
|
||||
&& left + width > x
|
||||
&& top < y
|
||||
&& top + height > y
|
||||
export function isInsideRectangle(
|
||||
x: number,
|
||||
y: number,
|
||||
left: number,
|
||||
top: number,
|
||||
width: number,
|
||||
height: number,
|
||||
): boolean {
|
||||
return left < x &&
|
||||
left + width > x &&
|
||||
top < y &&
|
||||
top + height > y
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,10 +139,10 @@ export function overlapBounding(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
|
||||
const bRight = b[0] + b[2]
|
||||
const bBottom = b[1] + b[3]
|
||||
|
||||
return a[0] > bRight
|
||||
|| a[1] > bBottom
|
||||
|| aRight < b[0]
|
||||
|| aBottom < b[1]
|
||||
return a[0] > bRight ||
|
||||
a[1] > bBottom ||
|
||||
aRight < b[0] ||
|
||||
aBottom < b[1]
|
||||
? false
|
||||
: true
|
||||
}
|
||||
@@ -151,16 +171,16 @@ export function containsRect(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
|
||||
const bRight = b[0] + b[2]
|
||||
const bBottom = b[1] + b[3]
|
||||
|
||||
const identical = a[0] === b[0]
|
||||
&& a[1] === b[1]
|
||||
&& aRight === bRight
|
||||
&& aBottom === bBottom
|
||||
const identical = a[0] === b[0] &&
|
||||
a[1] === b[1] &&
|
||||
aRight === bRight &&
|
||||
aBottom === bBottom
|
||||
|
||||
return !identical
|
||||
&& a[0] <= b[0]
|
||||
&& a[1] <= b[1]
|
||||
&& aRight >= bRight
|
||||
&& aBottom >= bBottom
|
||||
return !identical &&
|
||||
a[0] <= b[0] &&
|
||||
a[1] <= b[1] &&
|
||||
aRight >= bRight &&
|
||||
aBottom >= bBottom
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +189,11 @@ export function containsRect(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
|
||||
* @param direction Direction to add the offset to
|
||||
* @param out The {@link Point} to add the offset to
|
||||
*/
|
||||
export function addDirectionalOffset(amount: number, direction: LinkDirection, out: Point): void {
|
||||
export function addDirectionalOffset(
|
||||
amount: number,
|
||||
direction: LinkDirection,
|
||||
out: Point,
|
||||
): void {
|
||||
switch (direction) {
|
||||
case LinkDirection.LEFT:
|
||||
out[0] -= amount
|
||||
@@ -190,12 +214,17 @@ export function addDirectionalOffset(amount: number, direction: LinkDirection, o
|
||||
/**
|
||||
* Rotates an offset in 90° increments.
|
||||
*
|
||||
* Swaps/flips axis values of a 2D vector offset - effectively rotating {@link offset} by 90°
|
||||
* Swaps/flips axis values of a 2D vector offset - effectively rotating
|
||||
* {@link offset} by 90°
|
||||
* @param offset The zero-based offset to rotate
|
||||
* @param from Direction to rotate from
|
||||
* @param to Direction to rotate to
|
||||
*/
|
||||
export function rotateLink(offset: Point, from: LinkDirection, to: LinkDirection): void {
|
||||
export function rotateLink(
|
||||
offset: Point,
|
||||
from: LinkDirection,
|
||||
to: LinkDirection,
|
||||
): void {
|
||||
let x: number
|
||||
let y: number
|
||||
|
||||
@@ -253,15 +282,24 @@ export function rotateLink(offset: Point, from: LinkDirection, to: LinkDirection
|
||||
|
||||
/**
|
||||
* Check if a point is to to the left or right of a line.
|
||||
* Project a line from lineStart -> lineEnd. Determine if point is to the left or right of that projection.
|
||||
* Project a line from lineStart -> lineEnd. Determine if point is to the left
|
||||
* or right of that projection.
|
||||
* {@link https://www.geeksforgeeks.org/orientation-3-ordered-points/}
|
||||
* @param lineStart The start point of the line
|
||||
* @param lineEnd The end point of the line
|
||||
* @param point The point to check
|
||||
* @returns 0 if all three points are in a straight line, a negative value if point is to the left of the projected line, or positive if the point is to the right
|
||||
* @returns 0 if all three points are in a straight line, a negative value if
|
||||
* point is to the left of the projected line, or positive if the point is to
|
||||
* the right
|
||||
*/
|
||||
export function getOrientation(lineStart: ReadOnlyPoint, lineEnd: ReadOnlyPoint, x: number, y: number): number {
|
||||
return ((lineEnd[1] - lineStart[1]) * (x - lineEnd[0])) - ((lineEnd[0] - lineStart[0]) * (y - lineEnd[1]))
|
||||
export function getOrientation(
|
||||
lineStart: ReadOnlyPoint,
|
||||
lineEnd: ReadOnlyPoint,
|
||||
x: number,
|
||||
y: number,
|
||||
): number {
|
||||
return ((lineEnd[1] - lineStart[1]) * (x - lineEnd[0])) -
|
||||
((lineEnd[0] - lineStart[0]) * (y - lineEnd[1]))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,7 +330,10 @@ export function findPointOnCurve(
|
||||
out[1] = (c1 * a[1]) + (c2 * controlA[1]) + (c3 * controlB[1]) + (c4 * b[1])
|
||||
}
|
||||
|
||||
export function createBounds(objects: Iterable<Positionable>, padding: number = 10): ReadOnlyRect | null {
|
||||
export function createBounds(
|
||||
objects: Iterable<Positionable>,
|
||||
padding: number = 10,
|
||||
): ReadOnlyRect | null {
|
||||
const bounds = new Float32Array([Infinity, Infinity, -Infinity, -Infinity])
|
||||
|
||||
for (const obj of objects) {
|
||||
@@ -308,7 +349,7 @@ export function createBounds(objects: Iterable<Positionable>, padding: number =
|
||||
bounds[0] - padding,
|
||||
bounds[1] - padding,
|
||||
bounds[2] - bounds[0] + (2 * padding),
|
||||
bounds[3] - bounds[1] + (2 * padding)
|
||||
bounds[3] - bounds[1] + (2 * padding),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
104
src/polyfills.ts
104
src/polyfills.ts
@@ -1,8 +1,11 @@
|
||||
|
||||
//API *************************************************
|
||||
//like rect but rounded corners
|
||||
// API *************************************************
|
||||
// like rect but rounded corners
|
||||
export function loadPolyfills() {
|
||||
if (typeof (window) != "undefined" && window.CanvasRenderingContext2D && !window.CanvasRenderingContext2D.prototype.roundRect) {
|
||||
if (
|
||||
typeof window != "undefined" &&
|
||||
window.CanvasRenderingContext2D &&
|
||||
!window.CanvasRenderingContext2D.prototype.roundRect
|
||||
) {
|
||||
// @ts-expect-error Slightly broken polyfill - radius_low not impl. anywhere
|
||||
window.CanvasRenderingContext2D.prototype.roundRect = function (
|
||||
x,
|
||||
@@ -10,76 +13,73 @@ if (typeof (window) != "undefined" && window.CanvasRenderingContext2D && !window
|
||||
w,
|
||||
h,
|
||||
radius,
|
||||
radius_low
|
||||
radius_low,
|
||||
) {
|
||||
let top_left_radius = 0;
|
||||
let top_right_radius = 0;
|
||||
let bottom_left_radius = 0;
|
||||
let bottom_right_radius = 0;
|
||||
let top_left_radius = 0
|
||||
let top_right_radius = 0
|
||||
let bottom_left_radius = 0
|
||||
let bottom_right_radius = 0
|
||||
|
||||
if (radius === 0) {
|
||||
this.rect(x, y, w, h);
|
||||
return;
|
||||
this.rect(x, y, w, h)
|
||||
return
|
||||
}
|
||||
|
||||
if (radius_low === undefined)
|
||||
radius_low = radius;
|
||||
if (radius_low === undefined) radius_low = radius
|
||||
|
||||
//make it compatible with official one
|
||||
// make it compatible with official one
|
||||
if (radius != null && radius.constructor === Array) {
|
||||
if (radius.length == 1)
|
||||
top_left_radius = top_right_radius = bottom_left_radius = bottom_right_radius = radius[0];
|
||||
top_left_radius = top_right_radius = bottom_left_radius = bottom_right_radius = radius[0]
|
||||
else if (radius.length == 2) {
|
||||
top_left_radius = bottom_right_radius = radius[0];
|
||||
top_right_radius = bottom_left_radius = radius[1];
|
||||
top_left_radius = bottom_right_radius = radius[0]
|
||||
top_right_radius = bottom_left_radius = radius[1]
|
||||
} else if (radius.length == 4) {
|
||||
top_left_radius = radius[0]
|
||||
top_right_radius = radius[1]
|
||||
bottom_left_radius = radius[2]
|
||||
bottom_right_radius = radius[3]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
else if (radius.length == 4) {
|
||||
top_left_radius = radius[0];
|
||||
top_right_radius = radius[1];
|
||||
bottom_left_radius = radius[2];
|
||||
bottom_right_radius = radius[3];
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
else //old using numbers
|
||||
{
|
||||
top_left_radius = radius || 0;
|
||||
top_right_radius = radius || 0;
|
||||
bottom_left_radius = radius_low || 0;
|
||||
bottom_right_radius = radius_low || 0;
|
||||
} else {
|
||||
// old using numbers
|
||||
top_left_radius = radius || 0
|
||||
top_right_radius = radius || 0
|
||||
bottom_left_radius = radius_low || 0
|
||||
bottom_right_radius = radius_low || 0
|
||||
}
|
||||
|
||||
//top right
|
||||
this.moveTo(x + top_left_radius, y);
|
||||
this.lineTo(x + w - top_right_radius, y);
|
||||
this.quadraticCurveTo(x + w, y, x + w, y + top_right_radius);
|
||||
// top right
|
||||
this.moveTo(x + top_left_radius, y)
|
||||
this.lineTo(x + w - top_right_radius, y)
|
||||
this.quadraticCurveTo(x + w, y, x + w, y + top_right_radius)
|
||||
|
||||
//bottom right
|
||||
this.lineTo(x + w, y + h - bottom_right_radius);
|
||||
// bottom right
|
||||
this.lineTo(x + w, y + h - bottom_right_radius)
|
||||
this.quadraticCurveTo(
|
||||
x + w,
|
||||
y + h,
|
||||
x + w - bottom_right_radius,
|
||||
y + h
|
||||
);
|
||||
y + h,
|
||||
)
|
||||
|
||||
//bottom left
|
||||
this.lineTo(x + bottom_right_radius, y + h);
|
||||
this.quadraticCurveTo(x, y + h, x, y + h - bottom_left_radius);
|
||||
// bottom left
|
||||
this.lineTo(x + bottom_right_radius, y + h)
|
||||
this.quadraticCurveTo(x, y + h, x, y + h - bottom_left_radius)
|
||||
|
||||
//top left
|
||||
this.lineTo(x, y + bottom_left_radius);
|
||||
this.quadraticCurveTo(x, y, x + top_left_radius, y);
|
||||
};
|
||||
}//if
|
||||
// top left
|
||||
this.lineTo(x, y + bottom_left_radius)
|
||||
this.quadraticCurveTo(x, y, x + top_left_radius, y)
|
||||
}
|
||||
} // if
|
||||
|
||||
if (typeof window != "undefined" && !window["requestAnimationFrame"]) {
|
||||
if (typeof window != "undefined" && !window["requestAnimationFrame"]) {
|
||||
window.requestAnimationFrame =
|
||||
// @ts-expect-error Legacy code
|
||||
window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame ||
|
||||
function (callback) {
|
||||
window.setTimeout(callback, 1000 / 60);
|
||||
};
|
||||
}
|
||||
window.setTimeout(callback, 1000 / 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,16 +28,23 @@ interface LegacyMouseEvent {
|
||||
}
|
||||
|
||||
/** PointerEvent with canvasX/Y and deltaX/Y properties */
|
||||
export interface CanvasPointerEvent extends PointerEvent, CanvasMouseEvent { }
|
||||
export interface CanvasPointerEvent extends PointerEvent, CanvasMouseEvent {}
|
||||
|
||||
/** MouseEvent with canvasX/Y and deltaX/Y properties */
|
||||
export interface CanvasMouseEvent extends MouseEvent, Readonly<ICanvasPosition>, Readonly<IDeltaPosition>, LegacyMouseEvent { }
|
||||
export interface CanvasMouseEvent extends
|
||||
MouseEvent,
|
||||
Readonly<ICanvasPosition>,
|
||||
Readonly<IDeltaPosition>,
|
||||
LegacyMouseEvent {}
|
||||
|
||||
/** DragEvent with canvasX/Y and deltaX/Y properties */
|
||||
export interface CanvasDragEvent extends DragEvent, ICanvasPosition, IDeltaPosition { }
|
||||
export interface CanvasDragEvent extends
|
||||
DragEvent,
|
||||
ICanvasPosition,
|
||||
IDeltaPosition {}
|
||||
|
||||
export type CanvasEventDetail =
|
||||
GenericEventDetail
|
||||
| GenericEventDetail
|
||||
| DragggingCanvasEventDetail
|
||||
| ReadOnlyEventDetail
|
||||
| GroupDoubleClickEventDetail
|
||||
@@ -50,12 +57,12 @@ export interface GenericEventDetail {
|
||||
}
|
||||
|
||||
export interface OriginalEvent {
|
||||
originalEvent: CanvasPointerEvent,
|
||||
originalEvent: CanvasPointerEvent
|
||||
}
|
||||
|
||||
export interface EmptyReleaseEventDetail extends OriginalEvent {
|
||||
subType: "empty-release",
|
||||
linkReleaseContext: LinkReleaseContextExtended,
|
||||
subType: "empty-release"
|
||||
linkReleaseContext: LinkReleaseContextExtended
|
||||
}
|
||||
|
||||
export interface ConnectingWidgetLinkEventDetail {
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import type { ISlotType, Dictionary, INodeFlags, INodeInputSlot, INodeOutputSlot, Point, Size } from "../interfaces"
|
||||
import type {
|
||||
ISlotType,
|
||||
Dictionary,
|
||||
INodeFlags,
|
||||
INodeInputSlot,
|
||||
INodeOutputSlot,
|
||||
Point,
|
||||
Size,
|
||||
} from "../interfaces"
|
||||
import type { LGraph, LGraphState } from "../LGraph"
|
||||
import type { IGraphGroupFlags, LGraphGroup } from "../LGraphGroup"
|
||||
import type { LGraphNode, NodeId } from "../LGraphNode"
|
||||
@@ -60,7 +68,7 @@ export interface ISerialisedNode {
|
||||
export type ISerialisedGraph<
|
||||
TNode = ReturnType<LGraphNode["serialize"]>,
|
||||
TLink = ReturnType<LLink["serialize"]>,
|
||||
TGroup = ReturnType<LGraphGroup["serialize"]>
|
||||
TGroup = ReturnType<LGraphGroup["serialize"]>,
|
||||
> = {
|
||||
last_node_id: NodeId
|
||||
last_link_id: number
|
||||
@@ -82,7 +90,13 @@ export interface ISerialisedGroup {
|
||||
flags?: IGraphGroupFlags
|
||||
}
|
||||
|
||||
export type TClipboardLink = [targetRelativeIndex: number, originSlot: number, nodeRelativeIndex: number, targetSlot: number, targetNodeId: NodeId]
|
||||
export type TClipboardLink = [
|
||||
targetRelativeIndex: number,
|
||||
originSlot: number,
|
||||
nodeRelativeIndex: number,
|
||||
targetSlot: number,
|
||||
targetNodeId: NodeId,
|
||||
]
|
||||
|
||||
/** Items copied from the canvas */
|
||||
export interface ClipboardItems {
|
||||
|
||||
@@ -34,7 +34,13 @@ export interface IWidgetOptions<TValue = unknown> extends Record<string, unknown
|
||||
* Recommend declaration merging any properties that use IWidget (e.g. {@link LGraphNode.widgets}) with a new type alias.
|
||||
* @see ICustomWidget
|
||||
*/
|
||||
export type IWidget = IBooleanWidget | INumericWidget | IStringWidget | IMultilineStringWidget | IComboWidget | ICustomWidget
|
||||
export type IWidget =
|
||||
| IBooleanWidget
|
||||
| INumericWidget
|
||||
| IStringWidget
|
||||
| IMultilineStringWidget
|
||||
| IComboWidget
|
||||
| ICustomWidget
|
||||
|
||||
export interface IBooleanWidget extends IBaseWidget {
|
||||
type?: "toggle"
|
||||
@@ -63,7 +69,9 @@ export interface IStringWidget extends IBaseWidget {
|
||||
}
|
||||
|
||||
/** A widget with a string value and a multiline text input */
|
||||
export interface IMultilineStringWidget<TElement extends HTMLElement = HTMLTextAreaElement> extends IBaseWidget {
|
||||
export interface IMultilineStringWidget<TElement extends HTMLElement = HTMLTextAreaElement> extends
|
||||
IBaseWidget {
|
||||
|
||||
type?: "multiline"
|
||||
value: string
|
||||
|
||||
@@ -72,14 +80,15 @@ export interface IMultilineStringWidget<TElement extends HTMLElement = HTMLTextA
|
||||
}
|
||||
|
||||
/** A custom widget - accepts any value and has no built-in special handling */
|
||||
export interface ICustomWidget<TElement extends HTMLElement = HTMLElement> extends IBaseWidget<TElement> {
|
||||
export interface ICustomWidget<TElement extends HTMLElement = HTMLElement> extends
|
||||
IBaseWidget<TElement> {
|
||||
|
||||
type?: "custom"
|
||||
value: string | object
|
||||
|
||||
element?: TElement
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Valid widget types. TS cannot provide easily extensible type safety for this at present.
|
||||
* Override linkedWidgets[]
|
||||
@@ -117,11 +126,23 @@ export interface IBaseWidget<TElement extends HTMLElement = HTMLElement> {
|
||||
element?: TElement
|
||||
|
||||
// TODO: Confirm this format
|
||||
callback?(value: any, canvas?: LGraphCanvas, node?: LGraphNode, pos?: Point, e?: CanvasMouseEvent): void
|
||||
callback?(
|
||||
value: any,
|
||||
canvas?: LGraphCanvas,
|
||||
node?: LGraphNode,
|
||||
pos?: Point,
|
||||
e?: CanvasMouseEvent,
|
||||
): void
|
||||
onRemove?(): void
|
||||
beforeQueued?(): void
|
||||
|
||||
mouse?(event: CanvasMouseEvent, arg1: number[], node: LGraphNode): boolean
|
||||
draw?(ctx: CanvasRenderingContext2D, node: LGraphNode, widget_width: number, y: number, H: number): void
|
||||
draw?(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
node: LGraphNode,
|
||||
widget_width: number,
|
||||
y: number,
|
||||
H: number,
|
||||
): void
|
||||
computeSize?(width: number): Size
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import type { LGraphNode } from "../LGraphNode"
|
||||
/**
|
||||
* Finds the nodes that are farthest in all four directions, representing the boundary of the nodes.
|
||||
* @param nodes The nodes to check the edges of
|
||||
* @returns An object listing the furthest node (edge) in all four directions. `null` if no nodes were supplied or the first node was falsy.
|
||||
* @returns An object listing the furthest node (edge) in all four directions.
|
||||
* `null` if no nodes were supplied or the first node was falsy.
|
||||
*/
|
||||
export function getBoundaryNodes(nodes: LGraphNode[]): IBoundaryNodes | null {
|
||||
const valid = nodes?.find(x => x)
|
||||
@@ -30,7 +31,7 @@ export function getBoundaryNodes(nodes: LGraphNode[]): IBoundaryNodes | null {
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
left
|
||||
left,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,11 +58,11 @@ export function distributeNodes(nodes: LGraphNode[], horizontal?: boolean): void
|
||||
const sorted = [...nodes].sort((a, b) => a.pos[index] - b.pos[index])
|
||||
const lowest = sorted[0].pos[index]
|
||||
|
||||
const gap = ((highest - lowest) - total) / (nodeCount - 1)
|
||||
const gap = (highest - lowest - total) / (nodeCount - 1)
|
||||
let startAt = lowest
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
const node = sorted[i]
|
||||
node.pos[index] = startAt + (gap * i)
|
||||
node.pos[index] = startAt + gap * i
|
||||
startAt += node.size[index]
|
||||
}
|
||||
}
|
||||
@@ -72,17 +73,16 @@ export function distributeNodes(nodes: LGraphNode[], horizontal?: boolean): void
|
||||
* @param direction The edge to align nodes on
|
||||
* @param align_to The node to align all other nodes to. If undefined, the farthest node will be used.
|
||||
*/
|
||||
export function alignNodes(nodes: LGraphNode[], direction: Direction, align_to?: LGraphNode): void {
|
||||
export function alignNodes(
|
||||
nodes: LGraphNode[],
|
||||
direction: Direction,
|
||||
align_to?: LGraphNode,
|
||||
): void {
|
||||
if (!nodes) return
|
||||
|
||||
const boundary = align_to === undefined
|
||||
? getBoundaryNodes(nodes)
|
||||
: {
|
||||
top: align_to,
|
||||
right: align_to,
|
||||
bottom: align_to,
|
||||
left: align_to
|
||||
}
|
||||
: { top: align_to, right: align_to, bottom: align_to, left: align_to }
|
||||
|
||||
if (boundary === null) return
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite'
|
||||
import path from 'path'
|
||||
import dts from 'vite-plugin-dts'
|
||||
import { defineConfig } from "vite"
|
||||
import path from "path"
|
||||
import dts from "vite-plugin-dts"
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, 'src/litegraph'),
|
||||
name: 'litegraph.js',
|
||||
fileName: (format) => `litegraph.${format}.js`,
|
||||
formats: ['es', 'umd']
|
||||
entry: path.resolve(__dirname, "src/litegraph"),
|
||||
name: "litegraph.js",
|
||||
fileName: format => `litegraph.${format}.js`,
|
||||
formats: ["es", "umd"],
|
||||
},
|
||||
sourcemap: true,
|
||||
target: ['es2022'],
|
||||
target: ["es2022"],
|
||||
},
|
||||
esbuild: {
|
||||
minifyIdentifiers: false,
|
||||
@@ -20,18 +20,18 @@ export default defineConfig({
|
||||
},
|
||||
plugins: [
|
||||
dts({
|
||||
entryRoot: 'src',
|
||||
entryRoot: "src",
|
||||
insertTypesEntry: true,
|
||||
include: ['src/**/*.ts'],
|
||||
outDir: 'dist',
|
||||
aliasesExclude: ['@'],
|
||||
include: ["src/**/*.ts"],
|
||||
outDir: "dist",
|
||||
aliasesExclude: ["@"],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: { '@': '/src' },
|
||||
alias: { "@": "/src" },
|
||||
},
|
||||
test: {
|
||||
alias: { '@/': path.resolve(__dirname, './src/') },
|
||||
environment: 'jsdom',
|
||||
alias: { "@/": path.resolve(__dirname, "./src/") },
|
||||
environment: "jsdom",
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user