feat: ✨ poc of the doc widget idea

This commit is contained in:
Mel Massadian
2024-03-22 06:35:16 +01:00
parent 2465ffb0d3
commit fac7529d1f
6 changed files with 278 additions and 17 deletions
+8 -5
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
###
# File: __init__.py
# Project: comfy_mtb
@@ -43,7 +42,7 @@ __version__ = "0.2.0"
def extract_nodes_from_source(filename):
source_code = ""
with open(filename, "r", encoding="utf8") as file:
with open(filename, encoding="utf8") as file:
source_code = file.read()
nodes = []
@@ -81,7 +80,7 @@ def load_nodes():
module = importlib.import_module(
f".nodes.{module_name}", package=__package__
)
_nodes = getattr(module, "__nodes__")
_nodes = module.__nodes__
nodes.extend(_nodes)
log.debug(f"Imported {module_name} nodes")
@@ -97,7 +96,7 @@ def load_nodes():
if errors:
log.debug(
f"Some nodes failed to load:\n\t"
"Some nodes failed to load:\n\t"
+ "\n\t".join(errors)
+ "\n\n"
+ "Check that you properly installed the dependencies.\n"
@@ -127,6 +126,10 @@ if web_mtb.exists() and hasattr(nodes, "EXTENSION_WEB_DIRS"):
nodes, failed = load_nodes()
for node_class in nodes:
class_name = node_class.__name__
# fallback to __doc__
if not hasattr(node_class, "DESCRIPTION") and node_class.__doc__:
node_class.DESCRIPTION = node_class.__doc__
node_label = f"{get_label(class_name)} (mtb)"
NODE_CLASS_MAPPINGS[node_label] = node_class
NODE_DISPLAY_NAME_MAPPINGS[class_name] = node_label
@@ -147,7 +150,7 @@ for node_class in nodes:
)
log.debug(
f"Loaded the following nodes:\n\t"
"Loaded the following nodes:\n\t"
+ "\n\t".join(
f"{cyan_text(k)}: {blue_text(get_summary(doc)) if doc else '-'}"
for k, doc in NODE_CLASS_MAPPINGS_DEBUG.items()
+31 -1
View File
@@ -2,7 +2,7 @@ from ..log import log
class AnimationBuilder:
"""Simple maths for animation."""
"""Node built around the idea of values over a queue of frames."""
@classmethod
def INPUT_TYPES(cls):
@@ -21,6 +21,36 @@ class AnimationBuilder:
RETURN_NAMES = ("frame", "0-1 (scaled)", "count", "loop_ended")
CATEGORY = "mtb/animation"
FUNCTION = "build_animation"
DESCRIPTION = """
# Animation Builder
Check the
[wiki page](https://github.com/melMass/comfy_mtb/wiki/nodes-animation-builder)
for more info.
- This basic example should help to understand the meaning of
its inputs and outputs thanks to the [debug](nodes-debug) node.
![](https://github.com/melMass/comfy_mtb/assets/7041726/2b5c7e4f-372d-4494-9e73-abb2daa7cb36)
- In this other example Animation Builder is used in combination with
[Batch From History](https://github.com/melMass/comfy_mtb/wiki/nodes-batch-from-history)
to create a zoom-in animation on a static image
![](https://github.com/melMass/comfy_mtb/assets/7041726/77d37da1-0a8e-4519-a493-dfdef7f755ea)
## Inputs
| name | description |
| ---- | :----------:|
| total_frames | The number of frame to queue (this is multiplied by the `loop_count`)|
| scale_float | Convenience input to scale the normalized `current value` (a float between 0 and 1 lerp over the current queue length) |
| loop_count | The number of loops to queue |
| **Reset Button** | resets the internal counters, although the node is though around using its queue button it should still work fine when using the regular queue button of comfy |
| **Queue Button** | Convenience button to run the queues (`total_frames` * `loop_count`) |
"""
def build_animation(
self,
+7
View File
@@ -200,6 +200,13 @@ class TextToImage:
"""
fonts = {}
DESCRIPTION = """# Text to Image
This node look for any font files in comfy_dir/fonts.
by default it fallsback to a default font.
![img](https://i.imgur.com/3GT92hy.gif)
"""
def __init__(self):
# - This is executed when the graph is executed,
+221 -4
View File
@@ -13,7 +13,7 @@ import { app } from '../../scripts/app.js'
export function makeUUID() {
let dt = new Date().getTime()
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (dt + Math.random() * 16) % 16 | 0
const r = ((dt + Math.random() * 16) % 16) | 0
dt = Math.floor(dt / 16)
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)
})
@@ -65,8 +65,8 @@ function createLogger(emoji, color, consoleMethod = 'log') {
}
}
export const infoLogger = createLogger('ℹ️', 'yellow')
export const warnLogger = createLogger('⚠️', 'orange', 'warn')
export const infoLogger = createLogger('i', 'yellow')
export const warnLogger = createLogger('!', 'orange', 'warn')
export const errorLogger = createLogger('🔥', 'red', 'error')
export const successLogger = createLogger('✅', 'green')
@@ -495,8 +495,225 @@ export const addDeprecation = (nodeType, reason) => {
reason: 'font-size:1.2em',
}
console.log(
`%c⚠️ ${title} is deprecated:%c ${reason}`,
`%c! ${title} is deprecated:%c ${reason}`,
styles.title,
styles.reason,
)
}
const create_documentation_stylesheet = () => {
const tag = 'mtb-documentation-stylesheet'
let styleTag = document.head.querySelector(tag)
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.type = 'text/css'
styleTag.id = tag
styleTag.innerHTML = `
.documentation-popup {
background: var(--bg-color);
position: absolute;
color: var(--fg-color);
font: 12px monospace;
line-height: 1.5em;
padding: 3px;
border-radius: 4px;
pointer-events: "inherit";
z-index: 5;
overflow:scroll;
}
.documentation-popup img {
max-width: 100%;
}
.documentation-popup table {
border-collapse: collapse;
border: 1px var(--border-color) solid;
}
.documentation-popup th,
.documentation-popup td {
border: 1px var(--border-color) solid;
}
.documentation-popup th {
background-color: var(--comfy-input-bg);
}
`
document.head.appendChild(styleTag)
}
}
let documentationConverter
/** Add documentation widget to the selected node */
export const addDocumentation = (
nodeData,
nodeType,
opts = { icon_size: 14, icon_margin: 4 },
) => {
if (!documentationConverter) {
documentationConverter = new showdown.Converter({
tables: true,
strikethrough: true,
emoji: true,
ghCodeBlocks: true,
tasklists: true,
ghMentions: true,
smoothLivePreview: true,
simplifiedAutoLink: true,
parseImgDimensions: true,
openLinksInNewWindow: true,
})
}
opts = opts || {}
const iconSize = opts.icon_size ? opts.icon_size : 14
const iconMargin = opts.icon_margin ? opts.icon_margin : 4
let docElement = null
let offsetX = 0
let offsetY = 0
if (!nodeData.description) {
return
}
const drawFg = nodeType.prototype.onDrawForeground
nodeType.prototype.onDrawForeground = function (ctx, canvas) {
const r = drawFg ? drawFg.apply(this, arguments) : undefined
if (this.flags.collapsed) return r
// icon position
const x = this.size[0] - iconSize - iconMargin
// const y = iconMargin * 1.5
// const questionMark = new Path2D(
// 'm15.901 25.36h3.84v-3.84h-3.84v3.84zm1.92-15.36c-2.88 0-5.76 2.88-5.76 5.76h3.84c0-.96.96-1.92 1.92-1.92s1.92.96 1.92 1.92c0 1.92-3.84 1.92-3.84 3.84h3.84c1.92-.66 3.84-1.92 3.84-4.8s-2.88-4.8-5.76-4.8zm0-7.68c-8.49 0-15.36 6.87-15.36 15.36s6.87 15.36 15.36 15.36 15.36-6.87 15.36-15.36-6.87-15.36-15.36-15.36zm0 26.88c-6.36 0-11.52-5.16-11.52-11.52s5.16-11.52 11.52-11.52 11.52 5.16 11.52 11.52-5.16 11.52-11.52 11.52z',
// )
//
// ctx.save()
if (this.show_doc && docElement === null) {
create_documentation_stylesheet()
docElement = document.createElement('div')
docElement.classList.add('documentation-popup')
docElement.innerHTML = documentationConverter.makeHtml(
nodeData.description,
)
// resize handle
const resizeHandle = document.createElement('div')
resizeHandle.style.width = '10px'
resizeHandle.style.height = '10px'
resizeHandle.style.background = 'gray'
resizeHandle.style.position = 'absolute'
resizeHandle.style.bottom = '0'
resizeHandle.style.right = '0'
resizeHandle.style.cursor = 'se-resize'
// TODO: fix resize logic
docElement.appendChild(resizeHandle)
let isResizing = false
let startX, startY, startWidth, startHeight
resizeHandle.addEventListener('mousedown', function (e) {
e.stopPropagation()
isResizing = true
startX = e.clientX
startY = e.clientY
startWidth = parseInt(
document.defaultView.getComputedStyle(docElement).width,
10,
)
startHeight = parseInt(
document.defaultView.getComputedStyle(docElement).height,
10,
)
})
document.addEventListener('mousemove', function (e) {
if (!isResizing) return
const newWidth = startWidth + e.clientX - startX
const newHeight = startHeight + e.clientY - startY
offsetX += newWidth - startWidth
offsetY += newHeight - startHeight
startWidth = newWidth
startHeight = newHeight
})
document.addEventListener('mouseup', function () {
isResizing = false
})
document.body.appendChild(docElement)
} else if (!this.show_doc && docElement !== null) {
docElement.parentNode.removeChild(docElement)
docElement = null
}
if (this.show_doc && docElement !== 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, scaleY)
.multiplySelf(ctx.getTransform())
.translateSelf(this.size[0] * scaleX, 0)
.translateSelf(10, -32)
const scale = new DOMMatrix().scaleSelf(transform.a, transform.d)
Object.assign(docElement.style, {
transformOrigin: '0 0',
transform: scale,
left: `${transform.a + transform.e}px`,
top: `${transform.d + transform.f}px`,
width: `${this.size[0] * 2}px`,
// height: `${(widget.parent?.inputHeight || 32) - (margin * 2)}px`,
height: `${this.size[1] || this.parent?.inputHeight || 32}px`,
// background: !node.color ? "" : node.color,
// color: "blue", //!node.color ? "" : "white",
})
// docElement.style.left = 140 - rect.right + "px";
// docElement.style.top = rect.top + "px";
}
ctx.translate(x, iconSize - 34) // Position the icon on the canvas
ctx.scale(iconSize / 32, iconSize / 32) // Scale the icon to the desired size
ctx.strokeStyle = 'rgba(255,255,255,0.3)'
ctx.lineCap = 'round'
ctx.lineJoin = 'round'
ctx.lineWidth = 2.4
// ctx.stroke(questionMark);
ctx.font = '36px monospace'
ctx.fillText('?', 0, 24)
ctx.restore()
return r
}
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 - 34
if (
localPos[0] > iconX &&
localPos[0] < iconX + iconSize &&
localPos[1] > iconY &&
localPos[1] < iconY + iconSize
) {
// Pencil icon was clicked, open the editor
// this.openEditorDialog();
if (this.show_doc === undefined) {
this.show_doc = true
} else {
this.show_doc = !this.show_doc
}
return true // Return true to indicate the event was handled
}
return r // Return false to let the event propagate
// return r;
}
}
+5 -1
View File
@@ -17,7 +17,7 @@ import * as shared from './comfy_shared.js'
import { log } from './comfy_shared.js'
// NOTE: new widget types registered by MTB Widgets
const newTypes = [, /*'BOOL'*/ 'COLOR', 'BBOX']
const newTypes = [/*'BOOL'*/ , 'COLOR', 'BBOX']
const deprecated_nodes = {
// 'Animation Builder':
@@ -572,6 +572,10 @@ const mtb_widgets = {
if (!nodeData.name.endsWith('(mtb)')) {
return
}
// console.log('MTB Node', { description: nodeData.description, nodeType })
shared.addDocumentation(nodeData, nodeType)
const deprecation = deprecated_nodes[nodeData.name.replace(' (mtb)', '')]
if (deprecation) {
+6 -6
View File
@@ -171,10 +171,10 @@ class NotePlus extends LiteGraph.LGraphNode {
// Create a new Path2D object from SVG path data
const pencilPath = new Path2D(
'M21.28 6.4l-9.54 9.54c-.95.95-3.77 1.39-4.4.76-.63-.63-.2-3.45.75-4.4l9.55-9.55a2.58 2.58 0 1 1 3.64 3.65z'
'M21.28 6.4l-9.54 9.54c-.95.95-3.77 1.39-4.4.76-.63-.63-.2-3.45.75-4.4l9.55-9.55a2.58 2.58 0 1 1 3.64 3.65z',
)
const folderPath = new Path2D(
'M11 4H6a4 4 0 0 0-4 4v10a4 4 0 0 0 4 4h11c2.21 0 3-1.8 3-4v-5'
'M11 4H6a4 4 0 0 0-4 4v10a4 4 0 0 0 4 4h11c2.21 0 3-1.8 3-4v-5',
)
// Draw the paths
@@ -222,7 +222,7 @@ class NotePlus extends LiteGraph.LGraphNode {
(me) => successLogger(`Updating edit_mode`, me),
{
values: ['html', 'markdown', 'raw'],
}
},
)
this.css_widget = this.addWidget('text', 'CSS', DEFAULT_CSS, (val) => {
@@ -409,7 +409,7 @@ class NotePlus extends LiteGraph.LGraphNode {
loadAceEditor() {
shared
.loadScript(
'https://cdn.jsdelivr.net/npm/ace-builds@1.16.0/src-min-noconflict/ace.min.js'
'https://cdn.jsdelivr.net/npm/ace-builds@1.16.0/src-min-noconflict/ace.min.js',
)
.catch((e) => {
errorLogger(e)
@@ -557,7 +557,7 @@ class NotePlus extends LiteGraph.LGraphNode {
}
calculateHeight() {
this.calculated_height = shared.calculateTotalChildrenHeight(
this.html_widget.element
this.html_widget.element,
)
this.setDirtyCanvas(true, true)
}
@@ -566,7 +566,7 @@ class NotePlus extends LiteGraph.LGraphNode {
// this.html_widget.element.style = css
const scopedCss = this.scopeCss(
`${CSS_RESET}\n${css}`,
`note-plus-${this.uuid}`
`note-plus-${this.uuid}`,
)
const cssDom = this.getCssDom()