mirror of
https://git.datalinker.icu/kijai/ComfyUI-KJNodes.git
synced 2025-12-10 05:15:05 +08:00
378 lines
12 KiB
JavaScript
378 lines
12 KiB
JavaScript
import { app } from '../../../scripts/app.js'
|
|
|
|
|
|
export const loadScript = (
|
|
FILE_URL,
|
|
async = true,
|
|
type = 'text/javascript',
|
|
) => {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
// Check if the script already exists
|
|
const existingScript = document.querySelector(`script[src="${FILE_URL}"]`)
|
|
if (existingScript) {
|
|
resolve({ status: true, message: 'Script already loaded' })
|
|
return
|
|
}
|
|
|
|
const scriptEle = document.createElement('script')
|
|
scriptEle.type = type
|
|
scriptEle.async = async
|
|
scriptEle.src = FILE_URL
|
|
|
|
scriptEle.addEventListener('load', (ev) => {
|
|
resolve({ status: true })
|
|
})
|
|
|
|
scriptEle.addEventListener('error', (ev) => {
|
|
reject({
|
|
status: false,
|
|
message: `Failed to load the script ${FILE_URL}`,
|
|
})
|
|
})
|
|
|
|
document.body.appendChild(scriptEle)
|
|
} catch (error) {
|
|
reject(error)
|
|
}
|
|
})
|
|
}
|
|
const create_documentation_stylesheet = () => {
|
|
const tag = 'kj-splineditor-stylesheet'
|
|
|
|
let styleTag = document.head.querySelector(tag)
|
|
|
|
if (!styleTag) {
|
|
styleTag = document.createElement('style')
|
|
styleTag.type = 'text/css'
|
|
styleTag.id = tag
|
|
styleTag.innerHTML = `
|
|
.spline-editor {
|
|
background: var(--comfy-menu-bg);
|
|
position: absolute;
|
|
color: var(--fg-color);
|
|
font: 12px monospace;
|
|
line-height: 1.5em;
|
|
padding: 10px;
|
|
z-index: 5;
|
|
overflow: hidden;
|
|
border-radius: 10px;
|
|
border-style: solid;
|
|
border-width: medium;
|
|
border-color: var(--border-color);
|
|
height: 512px;
|
|
width: 512px;
|
|
max-height: 100%;
|
|
/* Scrollbar styling for Chrome */
|
|
&::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
&::-webkit-scrollbar-track {
|
|
background: var(--bg-color);
|
|
}
|
|
&::-webkit-scrollbar-thumb {
|
|
background-color: var(--fg-color);
|
|
border-radius: 6px;
|
|
border: 3px solid var(--bg-color);
|
|
}
|
|
|
|
/* Scrollbar styling for Firefox */
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--fg-color) var(--bg-color);
|
|
a {
|
|
color: yellow;
|
|
}
|
|
a:visited {
|
|
color: orange;
|
|
}
|
|
a:hover {
|
|
color: red;
|
|
}
|
|
}
|
|
`
|
|
document.head.appendChild(styleTag)
|
|
}
|
|
}
|
|
|
|
loadScript('/kjweb_async/svg-path-properties.min.js').catch((e) => {
|
|
console.log(e)
|
|
})
|
|
|
|
class SplineEditorWidget {
|
|
constructor(inputName, defaultValue) {
|
|
//this.name = inputName || "Spline";
|
|
//this._value = defaultValue || [{ x: 0, y: 0 }];
|
|
this.type = "SPLINE";
|
|
//this.selectedPointIndex = null;
|
|
this.resize
|
|
}
|
|
|
|
computeSize(width) {
|
|
return [width, 300];
|
|
}
|
|
|
|
configure(data) {
|
|
console.log(data)
|
|
}
|
|
|
|
value() {
|
|
console.debug('Returning value', this._value)
|
|
return this._value
|
|
}
|
|
setValue(value) {
|
|
console.debug('Setting value', value)
|
|
this._value = value
|
|
}
|
|
}
|
|
|
|
app.registerExtension({
|
|
name: 'KJNodes.curves',
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData) {
|
|
if (nodeData.name == 'SplineEditor') {
|
|
addElement(nodeData, nodeType);
|
|
}
|
|
},
|
|
// getCustomWidgets: function () {
|
|
// return {
|
|
// SPLINE: (node, inputName, inputData, app) => {
|
|
// console.log(inputName)
|
|
// console.log(inputData)
|
|
// console.log("Registering KJNodes curve widget");
|
|
// return {
|
|
// widget: node.addCustomWidget(
|
|
// new SplineEditorWidget(inputName, inputData[1]?.default)
|
|
// ),
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
})
|
|
|
|
export const addElement = (nodeData,nodeType, opts = { icon_size: 24, icon_margin: 4 }) => {
|
|
opts = opts || {}
|
|
const iconSize = opts.icon_size ? opts.icon_size : 14
|
|
const iconMargin = opts.icon_margin ? opts.icon_margin : 4
|
|
console.log("Creating spline editor")
|
|
let splineEditor = null
|
|
let vis = null
|
|
|
|
const drawFg = nodeType.prototype.onDrawForeground
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
this.coordWidget = this.widgets.find(w => w.name === "coordinates");
|
|
}
|
|
nodeType.prototype.onDrawForeground = function (ctx) {
|
|
const r = drawFg ? drawFg.apply(this, arguments) : undefined
|
|
if (this.flags.collapsed) return r
|
|
const x = this.size[0] - iconSize - iconMargin
|
|
if (splineEditor === null) {
|
|
create_documentation_stylesheet()
|
|
splineEditor = document.createElement('div');
|
|
splineEditor.classList.add('spline-editor');
|
|
|
|
// close button
|
|
const closeButton = document.createElement('div');
|
|
closeButton.textContent = '❌';
|
|
closeButton.style.position = 'absolute';
|
|
closeButton.style.top = '0';
|
|
closeButton.style.right = '0';
|
|
closeButton.style.cursor = 'pointer';
|
|
closeButton.style.padding = '5px';
|
|
closeButton.style.color = 'red';
|
|
closeButton.style.fontSize = '12px';
|
|
closeButton.addEventListener('mousedown', (e) => {
|
|
e.stopPropagation();
|
|
this.show_doc = !this.show_doc
|
|
splineEditor.parentNode.removeChild(splineEditor)
|
|
splineEditor = null
|
|
});
|
|
|
|
splineEditor.appendChild(closeButton)
|
|
|
|
document.body.appendChild(splineEditor)
|
|
|
|
var w = 512
|
|
var h = 512
|
|
var i = 3
|
|
loadScript('/kjweb_async/protovis.min.js').then(() => {
|
|
console.log('Protovis loaded successfully');
|
|
|
|
|
|
var points = pv.range(1, 5).map(i => ({
|
|
x: i * w / 5,
|
|
y: 50 + Math.random() * (h - 100)
|
|
}));
|
|
var interpolate = "cardinal"
|
|
var segmented = false
|
|
vis = new pv.Panel()
|
|
.width(w)
|
|
.height(h)
|
|
.fillStyle("var(--comfy-menu-bg)")
|
|
//.strokeStyle("orange")
|
|
.lineWidth(0)
|
|
.antialias(false)
|
|
.margin(2)
|
|
.event("mousedown", function() {
|
|
i = points.push(this.mouse()) - 1;
|
|
return this;
|
|
});
|
|
vis.add(pv.Rule)
|
|
.data(pv.range(0, 8, .5))
|
|
.bottom(d => d * 70 + 9.5)
|
|
.strokeStyle("white")
|
|
.lineWidth(2)
|
|
|
|
vis.add(pv.Line)
|
|
.data(() => points)
|
|
.left(d => d.x)
|
|
.top(d => d.y)
|
|
.interpolate(() => interpolate)
|
|
.segmented(() => segmented)
|
|
.strokeStyle(pv.Colors.category10().by(pv.index))
|
|
.tension(0.5)
|
|
.lineWidth(3);
|
|
|
|
vis.add(pv.Dot)
|
|
.data(() => points)
|
|
.left(d => d.x)
|
|
.top(d => d.y)
|
|
.radius(7)
|
|
.cursor("move")
|
|
.strokeStyle(function() { return i == this.index ? "#ff7f0e" : "#1f77b4"; })
|
|
.fillStyle(function() { return "rgba(100, 100, 100, 0.2)"; })
|
|
//.anchor("center").add(pv.Label)
|
|
//.font(d => Math.sqrt(d[2]) * 20 + "px sans-serif")
|
|
// .text(d => d[2])
|
|
.event("mousedown", pv.Behavior.drag())
|
|
.event("dragstart", function() {
|
|
i = this.index;
|
|
return this;
|
|
})
|
|
.event("drag", vis)
|
|
|
|
pv.listen(window, "mousedown", () => {
|
|
window.focus();
|
|
//logPathLength();
|
|
});
|
|
pv.listen(window, "mouseup", () => {
|
|
//logPathLength();
|
|
if (svgPathElement !== null) {
|
|
let coords = samplePoints(svgPathElement, points.length);
|
|
let coordsString = JSON.stringify(coords);
|
|
if (this.coordWidget) this.coordWidget.value = coordsString;
|
|
}
|
|
});
|
|
pv.listen(window, "keydown", (e) => {
|
|
console.log("key code: " + e.keyCode)
|
|
// code 8 is backspace, code 46 is delete
|
|
if ((e.keyCode == 16 || e.keyCode == 46) && (i >= 0)) {
|
|
points.splice(i--, 1);
|
|
vis.render();
|
|
e.preventDefault();
|
|
}
|
|
})
|
|
vis.render();
|
|
var svgElement = vis.canvas();
|
|
var svgPathElement = document.querySelector('path');
|
|
splineEditor.appendChild(svgElement);
|
|
|
|
})
|
|
.catch((error) => {
|
|
console.error('Failed to load Protovis:', error);
|
|
});
|
|
}
|
|
if (this.show_doc && splineEditor !== null && vis !== null) {
|
|
const rect = ctx.canvas.getBoundingClientRect()
|
|
const scaleX = rect.width / ctx.canvas.width
|
|
const scaleY = rect.height / ctx.canvas.height
|
|
|
|
const transform = new DOMMatrix()
|
|
.scaleSelf(scaleX -0.05, scaleY -0.05)
|
|
.multiplySelf(ctx.getTransform())
|
|
.translateSelf(470, -10)
|
|
|
|
const scale = new DOMMatrix()
|
|
.scaleSelf(transform.a, transform.d);
|
|
|
|
const styleObject = {
|
|
transformOrigin: '0 0',
|
|
transform: scale,
|
|
left: `${transform.a + transform.e}px`,
|
|
top: `${transform.d + transform.f}px`,
|
|
};
|
|
Object.assign(splineEditor.style, styleObject);
|
|
|
|
//vis.render();
|
|
|
|
//logPathLength(svgPathElement);
|
|
|
|
|
|
}
|
|
ctx.save()
|
|
ctx.translate(x - 2, iconSize - 45)
|
|
ctx.scale(iconSize / 32, iconSize / 32)
|
|
ctx.strokeStyle = 'rgba(255,255,255,0.3)'
|
|
ctx.lineCap = 'round'
|
|
ctx.lineJoin = 'round'
|
|
ctx.lineWidth = 2.4
|
|
ctx.font = 'bold 36px monospace'
|
|
ctx.fillStyle = 'orange';
|
|
ctx.fillText('📈', 0, 24)
|
|
ctx.restore()
|
|
return r
|
|
}
|
|
// handle clicking of the icon
|
|
const mouseDown = nodeType.prototype.onMouseDown
|
|
nodeType.prototype.onMouseDown = function (e, localPos, canvas) {
|
|
const r = mouseDown ? mouseDown.apply(this, arguments) : undefined
|
|
const iconX = this.size[0] - iconSize - iconMargin
|
|
const iconY = iconSize - 45
|
|
if (
|
|
localPos[0] > iconX &&
|
|
localPos[0] < iconX + iconSize &&
|
|
localPos[1] > iconY &&
|
|
localPos[1] < iconY + iconSize
|
|
) {
|
|
if (this.show_doc === undefined) {
|
|
this.show_doc = true
|
|
} else {
|
|
this.show_doc = !this.show_doc
|
|
}
|
|
return true;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
}
|
|
|
|
function logPathLength(svgPathElement) {
|
|
//var svgPathElement = document.querySelector('path');
|
|
// If the SVG path element exists, get the path string from the 'd' attribute
|
|
if (svgPathElement) {
|
|
var pathString = svgPathElement.getAttribute('d');
|
|
|
|
// Now, you can use the svg-path-properties library on this path string.
|
|
const properties = new svgPathProperties.svgPathProperties(pathString);
|
|
const length = properties.getTotalLength();
|
|
// ...and other properties as needed
|
|
console.log(length);
|
|
}
|
|
}
|
|
|
|
function samplePoints(svgPathElement, numSamples) {
|
|
var pathLength = svgPathElement.getTotalLength();
|
|
var points = [];
|
|
|
|
for (var i = 0; i < numSamples; i++) {
|
|
// Calculate the distance along the path for the current sample
|
|
var distance = (pathLength / (numSamples - 1)) * i;
|
|
|
|
// Get the point at the current distance
|
|
var point = svgPathElement.getPointAtLength(distance);
|
|
|
|
// Add the point to the array of points
|
|
points.push({ x: point.x, y: point.y });
|
|
}
|
|
//console.log(points);
|
|
return points;
|
|
} |