Files
Kosinkadink-ComfyUI-Animate…/web/js/documentation.js
T
Austin Mroz 68254c0590 Fix documentation scrollbar style leakage
The floating documentation applies styling to scrollbars to make them
take up less space and better match the styling of the canvas.

This styling is improperly configured and will leak to other areas in
comryui with scrollabrs such as sidebar.

This style code is only applied if a user has installed ADE but not VHS.
2025-03-12 17:46:10 -05:00

293 lines
11 KiB
JavaScript

import { app } from '../../../scripts/app.js'
function chainCallback(object, property, callback) {
if (object == undefined) {
//This should not happen.
console.error("Tried to add callback to non-existant object")
return;
}
if (property in object && object[property]) {
const callback_orig = object[property]
object[property] = function () {
const r = callback_orig.apply(this, arguments);
callback.apply(this, arguments);
return r
};
} else {
object[property] = callback;
}
}
var helpDOM;
function initHelpDOM() {
let parentDOM = document.createElement("div");
document.body.appendChild(parentDOM)
parentDOM.appendChild(helpDOM)
helpDOM.className = "litegraph";
let scrollbarStyle = document.createElement('style');
parentDOM.className = "VHS_floatinghelp"
scrollbarStyle.innerHTML = `
.VHS_floatinghelp {
scrollbar-width: 6px;
scrollbar-color: #0003 #0000;
&::-webkit-scrollbar {
background: transparent;
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #0005;
border-radius: 20px
}
&::-webkit-scrollbar-button {
display: none;
}
}
.VHS_loopedvideo::-webkit-media-controls-mute-button {
display:none;
}
.VHS_loopedvideo::-webkit-media-controls-fullscreen-button {
display:none;
}
`
parentDOM.appendChild(scrollbarStyle)
chainCallback(app.canvas, "onDrawForeground", function (ctx, visible_rect){
let n = helpDOM.node
if (!n || !n?.graph) {
parentDOM.style['left'] = '-5000px'
return
}
//draw : function(ctx, node, widgetWidth, widgetY, height) {
//update widget position, even if off screen
const transform = ctx.getTransform();
const scale = app.canvas.ds.scale;//gets the litegraph zoom
//calculate coordinates with account for browser zoom
const bcr = app.canvas.canvas.getBoundingClientRect()
const x = transform.e*scale/transform.a + bcr.x;
const y = transform.f*scale/transform.a + bcr.y;
//TODO: text reflows at low zoom. investigate alternatives
Object.assign(parentDOM.style, {
left: (x+(n.pos[0] + n.size[0]+15)*scale) + "px",
top: (y+(n.pos[1]-LiteGraph.NODE_TITLE_HEIGHT)*scale) + "px",
width: "400px",
minHeight: "100px",
maxHeight: "600px",
overflowY: 'scroll',
transformOrigin: '0 0',
transform: 'scale(' + scale + ',' + scale +')',
fontSize: '18px',
backgroundColor: LiteGraph.NODE_DEFAULT_BGCOLOR,
boxShadow: '0 0 10px black',
borderRadius: '4px',
padding: '3px',
zIndex: 3,
position: "absolute",
display: 'inline',
});
});
function setCollapse(el, doCollapse) {
if (doCollapse) {
el.children[0].children[0].innerHTML = '+'
Object.assign(el.children[1].style, {
color: '#CCC',
overflowX: 'hidden',
width: '0px',
minWidth: 'calc(100% - 20px)',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
})
for (let child of el.children[1].children) {
if (child.style.display != 'none'){
child.origDisplay = child.style.display
}
child.style.display = 'none'
}
} else {
el.children[0].children[0].innerHTML = '-'
Object.assign(el.children[1].style, {
color: '',
overflowX: '',
width: '100%',
minWidth: '',
textOverflow: '',
whiteSpace: '',
})
for (let child of el.children[1].children) {
child.style.display = child.origDisplay
}
}
}
helpDOM.collapseOnClick = function() {
let doCollapse = this.children[0].innerHTML == '-'
setCollapse(this.parentElement, doCollapse)
}
helpDOM.selectHelp = function(name, value) {
//attempt to navigate to name in help
function collapseUnlessMatch(items,t) {
var match = items.querySelector('[vhs_title="' + t + '"]')
if (!match) {
for (let i of items.children) {
if (i.innerHTML.slice(0,t.length+5).includes(t)) {
match = i
break
}
}
}
if (!match) {
return null
}
//For longer documentation items with fewer collapsable elements,
//scroll to make sure the entirety of the selected item is visible
//This has the unfortunate side effect of trying to scroll the main
//window if the documentation windows is forcibly offscreen,
//but it's easy to simply scroll the main window back and seems to
//have no visual side effects
match.scrollIntoView(false)
window.scrollTo(0,0)
for (let i of items.querySelectorAll('.VHS_collapse')) {
if (i.contains(match)) {
setCollapse(i, false)
} else {
setCollapse(i, true)
}
}
return match
}
let target = collapseUnlessMatch(helpDOM, name)
if (target && value) {
collapseUnlessMatch(target, value)
}
}
helpDOM.addHelp = function(node, nodeType, description) {
if (!description) {
return
}
//Pad computed size for the clickable question mark
let originalComputeSize = node.computeSize
node.computeSize = function() {
let size = originalComputeSize.apply(this, arguments)
if (!this.title) {
return size
}
let title_width = this.title.length * 0.6 * LiteGraph.NODE_TEXT_SIZE
size[0] = Math.max(size[0], title_width + LiteGraph.NODE_TITLE_HEIGHT)
return size
}
node.description = description
chainCallback(node, "onDrawForeground", function (ctx) {
//draw question mark
ctx.save()
ctx.font = 'bold 20px Arial'
ctx.fillText("?", this.size[0]-17, -8)
ctx.restore()
})
chainCallback(node, "onMouseDown", function (e, pos, canvas) {
//On click would be preferred, but this'll be good enough
if (pos[1] < 0 && pos[0] + LiteGraph.NODE_TITLE_HEIGHT > this.size[0]) {
//corner question mark clicked
if (helpDOM.node == this) {
helpDOM.node = undefined
} else {
helpDOM.node = this;
helpDOM.innerHTML = this.description || "no help provided ".repeat(20)
for (let e of helpDOM.querySelectorAll('.VHS_collapse')) {
e.children[0].onclick = helpDOM.collapseOnClick
e.children[0].style.cursor = 'pointer'
}
for (let e of helpDOM.querySelectorAll('.VHS_precollapse')) {
setCollapse(e, true)
}
}
return true
}
})
let timeout = null
chainCallback(node, "onMouseMove", function (e, pos, canvas) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
if (helpDOM.node != this) {
return
}
timeout = setTimeout(() => {
let n = this
if (pos[0] > 0 && pos[0] < n.size[0]
&& pos[1] > 0 && pos[1] < n.size[1]) {
//TODO: provide help specific to element clicked
let inputRows = Math.max(n.inputs.length, n.outputs.length)
if (pos[1] < LiteGraph.NODE_SLOT_HEIGHT * inputRows) {
let row = Math.floor((pos[1] - 7) / LiteGraph.NODE_SLOT_HEIGHT)
if (pos[0] < n.size[0]/2) {
if (row < n.inputs.length) {
helpDOM.selectHelp(n.inputs[row].name)
}
} else {
if (row < n.outputs.length) {
helpDOM.selectHelp(n.outputs[row].name)
}
}
} else {
//probably widget, but widgets have variable height.
let basey = LiteGraph.NODE_SLOT_HEIGHT * inputRows + 6
for (let w of n.widgets) {
if (w.y) {
basey = w.y
}
let wheight = LiteGraph.NODE_WIDGET_HEIGHT+4
if (w.computeSize) {
wheight = w.computeSize(n.size[0])[1]
}
if (pos[1] < basey + wheight) {
helpDOM.selectHelp(w.name, w.value)
break
}
basey += wheight
}
}
}
}, 500)
})
chainCallback(node, "onMouseLeave", function (e, pos, canvas) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
});
}
}
app.registerExtension({
name: "AnimateDiffEvolved.documentation",
async init() {
if (app.VHSHelp) {
helpDOM = app.VHSHelp
} else {
helpDOM = document.createElement("div");
initHelpDOM()
app.VHSHelp = helpDOM
}
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// NOTE: May need manual adjusting for the few non-namespaced nodes
if(nodeData?.name?.startsWith("ADE_") && nodeData.description) {
let description = nodeData.description
let el = document.createElement("div")
el.innerHTML = description
if (!el.children.length) {
//Is plaintext. Do minor convenience formatting
let chunks = description.split('\n')
nodeData.description = chunks[0]
description = chunks.join('<br>')
} else {
nodeData.description = el.querySelector('#VHS_shortdesc')?.innerHTML || el.children[1]?.firstChild?.innerHTML
}
chainCallback(nodeType.prototype, "onNodeCreated", function () {
helpDOM.addHelp(this, nodeType, description)
})
}
},
});