|
import { app } from "../../../scripts/app.js"; |
|
|
|
|
|
export const loadScript = ( |
|
FILE_URL, |
|
async = true, |
|
type = 'text/javascript', |
|
) => { |
|
return new Promise((resolve, reject) => { |
|
try { |
|
|
|
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) |
|
} |
|
}) |
|
} |
|
|
|
loadScript('/kjweb_async/marked.min.js').catch((e) => { |
|
console.log(e) |
|
}) |
|
loadScript('/kjweb_async/purify.min.js').catch((e) => { |
|
console.log(e) |
|
}) |
|
|
|
const categories = ["KJNodes", "SUPIR", "VoiceCraft", "Marigold", "IC-Light"]; |
|
app.registerExtension({ |
|
name: "KJNodes.HelpPopup", |
|
async beforeRegisterNodeDef(nodeType, nodeData) { |
|
|
|
if (app.ui.settings.getSettingValue("KJNodes.helpPopup") === false) { |
|
return; |
|
} |
|
try { |
|
categories.forEach(category => { |
|
if (nodeData?.category?.startsWith(category)) { |
|
addDocumentation(nodeData, nodeType); |
|
} |
|
else return |
|
}); |
|
} catch (error) { |
|
console.error("Error in registering KJNodes.HelpPopup", error); |
|
} |
|
}, |
|
}); |
|
|
|
const create_documentation_stylesheet = () => { |
|
const tag = 'kj-documentation-stylesheet' |
|
|
|
let styleTag = document.head.querySelector(tag) |
|
|
|
if (!styleTag) { |
|
styleTag = document.createElement('style') |
|
styleTag.type = 'text/css' |
|
styleTag.id = tag |
|
styleTag.innerHTML = ` |
|
.kj-documentation-popup { |
|
background: var(--comfy-menu-bg); |
|
position: absolute; |
|
color: var(--fg-color); |
|
font: 12px monospace; |
|
line-height: 1.5em; |
|
padding: 10px; |
|
border-radius: 10px; |
|
border-style: solid; |
|
border-width: medium; |
|
border-color: var(--border-color); |
|
z-index: 5; |
|
overflow: hidden; |
|
} |
|
.content-wrapper { |
|
overflow: auto; |
|
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) |
|
} |
|
} |
|
|
|
|
|
export const addDocumentation = ( |
|
nodeData, |
|
nodeType, |
|
opts = { icon_size: 14, icon_margin: 4 },) => { |
|
|
|
opts = opts || {} |
|
const iconSize = opts.icon_size ? opts.icon_size : 14 |
|
const iconMargin = opts.icon_margin ? opts.icon_margin : 4 |
|
let docElement = null |
|
let contentWrapper = null |
|
|
|
if (!nodeData.description) { |
|
return |
|
} |
|
|
|
const drawFg = nodeType.prototype.onDrawForeground |
|
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 (this.show_doc && docElement === null) { |
|
docElement = document.createElement('div') |
|
contentWrapper = document.createElement('div'); |
|
docElement.appendChild(contentWrapper); |
|
|
|
create_documentation_stylesheet() |
|
contentWrapper.classList.add('content-wrapper'); |
|
docElement.classList.add('kj-documentation-popup') |
|
|
|
|
|
contentWrapper.innerHTML = DOMPurify.sanitize(marked.parse(nodeData.description,)) |
|
|
|
|
|
const resizeHandle = document.createElement('div'); |
|
resizeHandle.style.width = '0'; |
|
resizeHandle.style.height = '0'; |
|
resizeHandle.style.position = 'absolute'; |
|
resizeHandle.style.bottom = '0'; |
|
resizeHandle.style.right = '0'; |
|
resizeHandle.style.cursor = 'se-resize'; |
|
|
|
|
|
const borderColor = getComputedStyle(document.documentElement).getPropertyValue('--border-color').trim(); |
|
resizeHandle.style.borderTop = '10px solid transparent'; |
|
resizeHandle.style.borderLeft = '10px solid transparent'; |
|
resizeHandle.style.borderBottom = `10px solid ${borderColor}`; |
|
resizeHandle.style.borderRight = `10px solid ${borderColor}`; |
|
|
|
docElement.appendChild(resizeHandle) |
|
let isResizing = false |
|
let startX, startY, startWidth, startHeight |
|
|
|
resizeHandle.addEventListener('mousedown', function (e) { |
|
e.preventDefault(); |
|
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); |
|
}, |
|
{ signal: this.docCtrl.signal }, |
|
); |
|
|
|
|
|
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'; |
|
|
|
docElement.appendChild(closeButton) |
|
|
|
closeButton.addEventListener('mousedown', (e) => { |
|
e.stopPropagation(); |
|
this.show_doc = !this.show_doc |
|
docElement.parentNode.removeChild(docElement) |
|
docElement = null |
|
if (contentWrapper) { |
|
contentWrapper.remove() |
|
contentWrapper = null |
|
} |
|
}, |
|
{ signal: this.docCtrl.signal }, |
|
); |
|
|
|
document.addEventListener('mousemove', function (e) { |
|
if (!isResizing) return; |
|
const scale = app.canvas.ds.scale; |
|
const newWidth = startWidth + (e.clientX - startX) / scale; |
|
const newHeight = startHeight + (e.clientY - startY) / scale;; |
|
docElement.style.width = `${newWidth}px`; |
|
docElement.style.height = `${newHeight}px`; |
|
}, |
|
{ signal: this.docCtrl.signal }, |
|
); |
|
|
|
document.addEventListener('mouseup', function () { |
|
isResizing = false |
|
}, |
|
{ signal: this.docCtrl.signal }, |
|
) |
|
|
|
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 * Math.max(1.0,window.devicePixelRatio) , 0) |
|
.translateSelf(10, -32) |
|
|
|
const scale = new DOMMatrix() |
|
.scaleSelf(transform.a, transform.d); |
|
const bcr = app.canvas.canvas.getBoundingClientRect() |
|
|
|
const styleObject = { |
|
transformOrigin: '0 0', |
|
transform: scale, |
|
left: `${transform.a + bcr.x + transform.e}px`, |
|
top: `${transform.d + bcr.y + transform.f}px`, |
|
}; |
|
Object.assign(docElement.style, styleObject); |
|
} |
|
|
|
ctx.save() |
|
ctx.translate(x - 2, iconSize - 34) |
|
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 |
|
} |
|
|
|
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 |
|
) { |
|
if (this.show_doc === undefined) { |
|
this.show_doc = true |
|
} else { |
|
this.show_doc = !this.show_doc |
|
} |
|
if (this.show_doc) { |
|
this.docCtrl = new AbortController() |
|
} else { |
|
this.docCtrl.abort() |
|
} |
|
return true; |
|
} |
|
return r; |
|
} |
|
const onRem = nodeType.prototype.onRemoved |
|
|
|
nodeType.prototype.onRemoved = function () { |
|
const r = onRem ? onRem.apply(this, []) : undefined |
|
|
|
if (docElement) { |
|
docElement.remove() |
|
docElement = null |
|
} |
|
|
|
if (contentWrapper) { |
|
contentWrapper.remove() |
|
contentWrapper = null |
|
} |
|
return r |
|
} |
|
} |