feat: ✨ poc of the doc widget idea
This commit is contained in:
+8
-5
@@ -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
@@ -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.
|
||||
|
||||

|
||||
|
||||
- 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
|
||||
|
||||

|
||||
|
||||
## 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,
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# - This is executed when the graph is executed,
|
||||
|
||||
+221
-4
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user