From ba1112ed9d6753d4c8bf700e7da3096baf823a35 Mon Sep 17 00:00:00 2001 From: "Alexander G. Morano" Date: Fri, 9 Aug 2024 12:12:47 -0700 Subject: [PATCH] track vectors as array split new help panel from old inline help tooltip+s support --- web/core/core_cozy_tips.js | 64 ++++--- web/core/core_experiment.js | 246 --------------------------- web/core/core_help.js | 331 +++++++----------------------------- web/core/core_help_old.js | 301 ++++++++++++++++++++++++++++++++ web/util/util_jov.js | 34 ++-- 5 files changed, 408 insertions(+), 568 deletions(-) delete mode 100644 web/core/core_experiment.js create mode 100644 web/core/core_help_old.js diff --git a/web/core/core_cozy_tips.js b/web/core/core_cozy_tips.js index 0bae17e..2c0255e 100644 --- a/web/core/core_cozy_tips.js +++ b/web/core/core_cozy_tips.js @@ -26,8 +26,6 @@ const JTooltipWidget = (app, name, opts) => { return w } -if(app.extensionManager) { - app.registerExtension({ name: "jovimetrix.help.tooltips", async getCustomWidgets() { @@ -86,6 +84,7 @@ app.registerExtension({ // jovian tooltip const widget_tooltip = (node?.widgets || []) .find(widget => widget.type === 'JTOOLTIP'); + if (!widget_tooltip) { hideTooltip(); return; @@ -93,9 +92,13 @@ app.registerExtension({ const tips = widget_tooltip.options.default || {}; const inputSlot = this.isOverNodeInput(node, this.graph_mouse[0], this.graph_mouse[1], [0, 0]); + + let tip; + let name; + if (inputSlot !== -1) { const slot = node.inputs[inputSlot]; - let tip = tips?.[slot.name]; + tip = tips?.[slot.name]; if (slot.widget) { const widget = node.widgets.find(w => w.name === slot.name); if (widget && widget.type.startsWith('converted-widget')) { @@ -105,38 +108,34 @@ app.registerExtension({ } } } - - const name = `inputs_${inputSlot}`; - if (widget_previous != name) { - hideTooltip(); - widget_previous = name; - } - return showTooltip(tip); - } - - const outputSlot = this.isOverNodeOutput(node, this.graph_mouse[0], this.graph_mouse[1], [0, 0]); - if (outputSlot !== -1) { - let tip = tips?.['outputs']?.[outputSlot]; - const name = `outputs_${outputSlot}`; - if (widget_previous != name) { - hideTooltip(); - widget_previous = name; + name = `inputs_${inputSlot}`; + } else { + const outputSlot = this.isOverNodeOutput(node, this.graph_mouse[0], this.graph_mouse[1], [0, 0]); + if (outputSlot !== -1) { + tip = tips?.['outputs']?.[outputSlot]; + name = `outputs_${outputSlot}`; + } else { + const widget = widgetGetHovered(); + if (widget && !widget.element) { + name = widget.name; + tip = tips?.[name]; + const def = widget.options?.default; + if (def) { + tip += ` (default: ${def})`; + } + } } - return showTooltip(tip); } - const widget = widgetGetHovered(); - if (widget && !widget.element) { - let tip = tips?.[widget.name]; - const def = widget.options?.default; - if (def) { - tip += ` (default: ${def})`; - } - if (widget_previous != widget.name) { - hideTooltip(); - widget_previous = widget.name; + if (widget_previous != name) { + hideTooltip(); + widget_previous = name; + if (tip) { + return showTooltip(tip); } - return showTooltip(tip); + return; + } else { + return; } hideTooltip(); }.bind(app.canvas); @@ -161,5 +160,4 @@ app.registerExtension({ }, }); }, -}); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/web/core/core_experiment.js b/web/core/core_experiment.js deleted file mode 100644 index 0586800..0000000 --- a/web/core/core_experiment.js +++ /dev/null @@ -1,246 +0,0 @@ -/** - * File: core_experiment.js - * Project: Jovimetrix - * - * crazy stuffs - */ - -import { app } from "../../../scripts/app.js" - -class Vec4ColorWidget { - constructor(...args) { - const [inputName, opts] = args - - this.name = inputName || 'Vec4Color' - this.type = 'VEC4COLOR' - this.selectedPointIndex = null - this.options = opts - this.value = this.value || [1.0, 1.0, 1.0, 1.0] - - this.hueSaturation = [null, null]; - this.brightness = 1.0; - this.brightnessWidthPct = 0.1; - - this.size = [200, 200] - this.wY = 0; - } - - draw(ctx, node, widgetWidth, widgetY) { - this.wY = widgetY; - this.wWidth = widgetWidth; - this.wHeight = node.size[1] - widgetY; - - let y = widgetY; - - let width = widgetWidth; - let height = node.size[1]; - - let barWidth = width * this.brightnessWidthPct; - let barHeight = height - y; - - let diskWidth = width - barWidth; - let diskHeight = height - y; - - let diskX = diskWidth * 0.5; - let diskY = diskHeight * 0.5 + y; - - let diskRadiusX = diskWidth * 0.5 - 1.0; - let diskRadiusY = diskHeight * 0.5 - 1.0; - - // generic function for drawing a canvas disc - function drawDisk (ctx, coords, radius, steps, colorCallback) { - let x = coords[0] || coords; // coordinate on x-axis - let y = coords[1] || coords; // coordinate on y-axis - let a = radius[0] || radius; // radius on x-axis - let b = radius[1] || radius; // radius on y-axis - let angle = 360; - let coef = Math.PI / 180; - - ctx.save(); - ctx.translate(x - a, y - b); - ctx.scale(a, b); - - steps = (angle / steps) || 360; - - for (; angle > 0 ; angle -= steps) { - ctx.beginPath(); - if (steps !== 360) { - ctx.moveTo(1, 1); // stroke - } - ctx.arc(1, 1, 1, - (angle - (steps / 2) - 1) * coef, - (angle + (steps / 2) + 1) * coef); - - if (colorCallback) { - colorCallback(ctx, angle); - } - else { - ctx.fillStyle = 'black'; - ctx.fill(); - } - } - ctx.restore(); - } - - // Draw HueSaturation Color Disk - drawDisk( - ctx, - [diskX, diskY], - [diskRadiusX, diskRadiusY], - 360, - function (ctx, angle) { - let gradient = ctx.createRadialGradient(1, 1, 1, 1, 1, 0); - gradient.addColorStop(0, 'hsl(' + (360 - angle + 0) + ', 100%, 50%)'); - gradient.addColorStop(1, '#fff'); - - ctx.fillStyle = gradient; - ctx.fill(); - } - ); - - // Draw brightness bar - let barX = width - barWidth; - let barY = y; - - let gradient = ctx.createLinearGradient(0, 0, 0, barHeight); - gradient.addColorStop(0, 'white'); - gradient.addColorStop(1, 'black'); - ctx.fillStyle = gradient; - ctx.fillRect(barX, barY, barWidth, barHeight); - - // Draw brightness position (based this.brightness) - // with two triangles at both size and a line in-between - if (this.brightness !== null) { - let bX = barX; - let bY = y + barHeight - this.brightness * barHeight; - let bColor = '#fff'; - ctx.strokeStyle = bColor; - ctx.fillStyle = bColor; - ctx.lineWidth = 1.0; - ctx.beginPath(); - ctx.moveTo(bX, bY); - ctx.lineTo(bX + barWidth, bY); - ctx.closePath(); - ctx.stroke(); - - // Left triangle - ctx.beginPath(); - ctx.moveTo(bX, bY); - ctx.lineTo(bX - 5, bY + 5); - ctx.lineTo(bX - 5, bY - 5); - ctx.closePath(); - ctx.fill(); - - // Right triangle - ctx.beginPath(); - ctx.moveTo(bX + barWidth, bY); - ctx.lineTo(bX + barWidth + 5, bY + 5); - ctx.lineTo(bX + barWidth + 5, bY - 5); - ctx.closePath(); - ctx.fill(); - } - - // Draw color position (based this.hueSaturation) - // stored in polar coordinates - if (this.hueSaturation[0] !== null && this.hueSaturation[1] !== null) { - let angle = this.hueSaturation[0]; - let dist = this.hueSaturation[1]; - let hX = -Math.cos(angle) * dist * diskRadiusX + diskX; - let hY = Math.sin(angle) * dist * diskRadiusY + diskY; - - ctx.fillStyle = '#000'; - ctx.beginPath(); - let radius = 4; - ctx.arc(hX, hY, radius, 0, 2 * Math.PI, false); - ctx.fill(); - - ctx.strokeStyle = '#000'; - ctx.lineWidth = 1.0; - ctx.beginPath(); - radius = 8; - ctx.arc(hX, hY, radius, 0, 2 * Math.PI, false); - ctx.stroke(); - } - - // Draw Selected Color (based this.value) - // as a rectangle with the color un the top left corner - let cW = this.wWidth - 50; - let cH = this.wY; - - ctx.fillStyle = 'rgba(' + Math.round(this.value[0] * 255) + ',' + Math.round(this.value[1] * 255) + ',' + Math.round(this.value[2] * 255) + ',' + this.value[3] + ')'; - ctx.fillRect(0, 0, cW, cH); - } - - mouse(e, pos, node) { - if (e.type === 'pointermove') { - // if pos is inside the brightness bar change brightness based on Y position - if (pos[0] > this.wWidth - this.wWidth * this.brightnessWidthPct) { - this.brightness = 1.0 - (pos[1] - this.wY) / this.wHeight; - this.brightness = Math.min(1.0, Math.max(0.0, this.brightness)); - } - else { - let barWidth = this.wWidth * this.brightnessWidthPct; - let diskWidth = this.wWidth - barWidth; - let diskHeight = this.wHeight - this.wY; - let diskX = diskWidth * 0.5; - let diskY = diskHeight * 0.5 + this.wY; - let diskRadiusX = diskWidth * 0.5 - 1.0; - let diskRadiusY = diskHeight * 0.5 - 1.0; - - let x = pos[0]; - let y = pos[1]; - // convert to polar coordinates - let dx = x - diskX; - let dy = y - diskY; - let angle = Math.atan2(dy, -dx); - let dist = Math.sqrt(dx * dx + dy * dy); - let maxDist = Math.min(diskRadiusX, diskRadiusY); - dist = Math.min(dist, maxDist); - angle = angle; - this.hueSaturation = [ angle, dist / maxDist]; - } - } - - // Convert hueSaturation and brightness to RGB - let h = this.hueSaturation[0] + Math.PI; - let s = this.hueSaturation[1]; - let v = this.brightness; - - // h *= 6; - var i = Math.floor(h), - f = h - i, - p = v * (1 - s), - q = v * (1 - f * s), - t = v * (1 - (1 - f) * s), - mod = i % 6; - - let r = [v, q, p, p, t, v][mod]; - let g = [t, v, v, q, p, p][mod]; - let b = [p, p, t, v, v, q][mod]; - - this.value = [r,g,b, 1.0]; - } - - computeSize(width) { - return [width, width - this.wY] - } - - async serializeValue(nodeId, widgetIndex) { - return [this.value[0], this.value[1], this.value[2], this.value[3]].toString(); - } -} - -app.registerExtension({ - name: "jovimetrix.experimental", - getCustomWidgets: () => { - return { - VEC4COLOR: (node, inputName, inputData, app) => { - return { - widget: node.addCustomWidget(new Vec4ColorWidget(inputName, inputData), ), - minWidth: 200, - minHeight: 300, - } - }, - } - } -}) \ No newline at end of file diff --git a/web/core/core_help.js b/web/core/core_help.js index f135902..a84a335 100644 --- a/web/core/core_help.js +++ b/web/core/core_help.js @@ -9,7 +9,6 @@ import { nodeCleanup } from '../util/util_node.js' import '../extern/shodown.min.js' const JOV_WEBWIKI_URL = "https://github.com/Amorano/Jovimetrix/wiki"; -// const JOV_HELP_URL = "https://raw.githubusercontent.com/Amorano/Jovimetrix-examples/master/node"; const CACHE_DOCUMENTATION = {}; if (!window.jovimetrixEvents) { @@ -76,276 +75,6 @@ async function load_help(name, custom_data) { return result; } -app.registerExtension({ - name: "jovimetrix.help", - setup() { - if(app?.extensionManager) { - const onSelectionChange = app.canvas.onSelectionChange; - app.canvas.onSelectionChange = function(selectedNodes) { - const me = onSelectionChange?.apply(this); - if (selectedNodes && Object.keys(selectedNodes).length > 0) { - const firstNodeKey = Object.keys(selectedNodes)[0]; - const firstNode = selectedNodes[firstNodeKey]; - const data = { - class: firstNode?.getNickname() || "unknown", - name: firstNode.type - } - const event = new CustomEvent('jovimetrixHelpRequested', { detail: data }); - jovimetrixEvents.dispatchEvent(event); - } - return me; - } - } - }, - async beforeRegisterNodeDef(nodeType, nodeData) { - if (!nodeData?.category?.startsWith("JOVIMETRIX")) { - return; - } - - // MENU HELP! - const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; - nodeType.prototype.getExtraMenuOptions = function (_, options) { - const me = getExtraMenuOptions?.apply(this, arguments); - if (!this.title.endsWith('πŸ§™πŸ½β€β™€οΈ')) { - const widget_tooltip = (this.widgets || []) - .find(widget => widget.type === 'JTOOLTIP'); - if (widget_tooltip) { - const tips = widget_tooltip.options.default || {}; - const url = tips['_']; - const help_menu = [{ - content: `HELP: ${this.title}`, - callback: () => { - LiteGraph.closeAllContextMenus(); - window.open(`${JOV_WEBWIKI_URL}/${url}`, '_blank'); - this.setDirtyCanvas(true, true); - } - }]; - if (help_menu.length) { - options.push(...help_menu, null); - } - } - } - return me; - } - - // if the old menu is present.... - if(new_menu == "Disabled" || !app.extensionManager) { - let opts = { icon_size: 14, icon_margin: 3 } - const iconSize = opts.icon_size ? opts.icon_size : 14; - const iconMargin = opts.icon_margin ? opts.icon_margin : 3; - let docElement = null; - let contentWrapper = null; - - const onNodeCreated = nodeType.prototype.onNodeCreated; - nodeType.prototype.onNodeCreated = async function () { - const me = onNodeCreated?.apply(this); - this.offsetX = 0; - this.offsetY = 0; - this.show_doc = false; - this.onRemoved = function () { - nodeCleanup(this); - if (docElement) { - docElement.remove(); - docElement = null; - } - if (contentWrapper) { - contentWrapper.remove(); - contentWrapper = null; - } - } - return me; - } - - const onDrawForeground = nodeType.prototype.onDrawForeground; - nodeType.prototype.onDrawForeground = async function (ctx) { - const me = onDrawForeground?.apply?.(this, arguments); - if (this.flags.collapsed) return me; - - const x = this.size[0] - iconSize - iconMargin - if (this.show_doc && docElement === null) { - - docElement = document.createElement('div') - docElement.classList.add('jov-panel-doc-popup'); - - const widget_tooltip = (this.widgets || []) - .find(widget => widget.type === 'JTOOLTIP'); - - if (widget_tooltip) { - const tips = widget_tooltip.options.default || {}; - const url_name = `jovimetrix/${tips['*']}`; - docElement.innerHTML = await load_help(url_name); - } - - // resize handle - 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 = '5px solid transparent'; - resizeHandle.style.borderLeft = '5px solid transparent'; - resizeHandle.style.borderBottom = `5px solid ${borderColor}`; - resizeHandle.style.borderRight = `5px 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.helpClose.signal }, - ); - - const buttons = document.createElement('div'); - // wiki page - const wikiButton = document.createElement('div'); - wikiButton.textContent = '🌐'; - wikiButton.style.position = 'absolute'; - wikiButton.style.top = '6px'; - wikiButton.style.right = '22px'; - wikiButton.style.cursor = 'pointer'; - wikiButton.style.padding = '4px'; - wikiButton.style.font = 'bold 14px monospace'; - wikiButton.addEventListener('mousedown', (e) => { - e.stopPropagation(); - const widget_tooltip = (this.widgets || []) - .find(widget => widget.type === 'JTOOLTIP'); - if (widget_tooltip) { - const tips = widget_tooltip.options.default || {}; - const url = tips['_']; - if (url !== undefined) { - window.open(`${JOV_WEBWIKI_URL}/${url}`, '_blank'); - } - wikiButton.tooltip = `WIKI PAGE: ${url}` - } - }); - buttons.appendChild(wikiButton); - - // close button - const closeButton = document.createElement('div'); - closeButton.textContent = '❌'; - closeButton.style.position = 'absolute'; - closeButton.style.top = '6px'; - closeButton.style.right = '4px'; - closeButton.style.cursor = 'pointer'; - closeButton.style.padding = '4px'; - closeButton.style.font = 'bold 14px monospace'; - closeButton.addEventListener('mousedown', (e) => { - e.stopPropagation(); - this.show_doc = !this.show_doc - if (contentWrapper) { - contentWrapper.remove() - contentWrapper = null - } - docElement.parentNode.removeChild(docElement) - docElement = null - }, - { signal: this.helpClose.signal }, - ); - buttons.appendChild(closeButton); - docElement.appendChild(buttons); - - 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.helpClose.signal }, - ); - document.addEventListener('mouseup', function () { - isResizing = false - }, - { signal: this.helpClose.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 styleObject = { - transformOrigin: '0 0', - transform: scale, - left: `${transform.a + transform.e}px`, - top: `${transform.d + transform.f}px`, - }; - Object.assign(docElement.style, styleObject); - } - - ctx.save(); - ctx.translate(x-3, -LiteGraph.NODE_TITLE_HEIGHT * 0.65); - ctx.scale(iconSize / 32, iconSize / 32); - ctx.font = `bold ${LiteGraph.NODE_TITLE_HEIGHT * 1.35}px monospace`; - ctx.fillText('πŸ›ˆ', 0, 24); // ℹ️ - ctx.restore() - return me; - } - - // ? clicked - const mouseDown = nodeType.prototype.onMouseDown - nodeType.prototype.onMouseDown = function (e, localPos) { - 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; - } - if (this.show_doc) { - this.helpClose = new AbortController() - } else { - this.helpClose.abort() - } - // Dispatch a custom event with the node name - const data = { - class: this.getNickname() || "jovimetrix", - name: this.type - } - const event = new CustomEvent('jovimetrixHelpRequested', { detail: data }); - jovimetrixEvents.dispatchEvent(event); - return true; - } - return r; - } - } - } -}) - let HELP_PANEL_CONTENT = `

SELECT A NODE TO SEE HELP

@@ -353,7 +82,7 @@ let HELP_PANEL_CONTENT = `
`; -if(new_menu != "Disabled" && app.extensionManager) { +if(new_menu != "Disabled" && app?.extensionManager) { let sidebarElement; const updateContent = async (node, data) => { @@ -381,5 +110,63 @@ if(new_menu != "Disabled" && app.extensionManager) { const node = `${event.detail.class}/${event.detail.name}`; await updateContent(node); }); + + app.registerExtension({ + name: "jovimetrix.help.select", + setup() { + const onSelectionChange = app.canvas.onSelectionChange; + app.canvas.onSelectionChange = function(selectedNodes) { + const me = onSelectionChange?.apply(this); + if (selectedNodes && Object.keys(selectedNodes).length > 0) { + const firstNodeKey = Object.keys(selectedNodes)[0]; + const firstNode = selectedNodes[firstNodeKey]; + const data = { + class: firstNode?.getNickname() || "unknown", + name: firstNode.type + } + const event = new CustomEvent('jovimetrixHelpRequested', { detail: data }); + jovimetrixEvents.dispatchEvent(event); + } + return me; + } + } + }); } +app.registerExtension({ + name: "jovimetrix.help2", + async beforeRegisterNodeDef(nodeType, nodeData) { + if (!nodeData?.category?.startsWith("JOVIMETRIX")) { + return; + } + + // MENU HELP! + const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; + nodeType.prototype.getExtraMenuOptions = function (_, options) { + const me = getExtraMenuOptions?.apply(this, arguments); + if (this.title.endsWith('πŸ§™πŸ½β€β™€οΈ')) { + return me; + } + + const widget_tooltip = (this.widgets || []) + .find(widget => widget.type === 'JTOOLTIP'); + console.info(widget_tooltip) + if (widget_tooltip) { + const tips = widget_tooltip.options.default || {}; + const url = tips['_']; + const help_menu = [{ + content: `HELP: ${this.title}`, + callback: () => { + LiteGraph.closeAllContextMenus(); + window.open(`${JOV_WEBWIKI_URL}/${url}`, '_blank'); + this.setDirtyCanvas(true, true); + } + }]; + if (help_menu.length) { + options.push(...help_menu, null); + } + } + return me; + } + } +}); diff --git a/web/core/core_help_old.js b/web/core/core_help_old.js new file mode 100644 index 0000000..f4cf701 --- /dev/null +++ b/web/core/core_help_old.js @@ -0,0 +1,301 @@ +/** + * File: core_help_old.js + * Project: Jovimetrix + * code based on mtb nodes by Mel Massadian https://github.com/melMass/comfy_mtb/ + */ + +import { app } from '../../../scripts/app.js' +import { nodeCleanup } from '../util/util_node.js' +import '../extern/shodown.min.js' + +const JOV_WEBWIKI_URL = "https://github.com/Amorano/Jovimetrix/wiki"; +// const JOV_HELP_URL = "https://raw.githubusercontent.com/Amorano/Jovimetrix-examples/master/node"; +const CACHE_DOCUMENTATION = {}; + +if (!window.jovimetrixEvents) { + window.jovimetrixEvents = new EventTarget(); +} +const jovimetrixEvents = window.jovimetrixEvents; + +const new_menu = app.ui.settings.getSettingValue("Comfy.UseNewMenu", "Disabled"); + +const documentationConverter = new showdown.Converter({ + tables: true, + strikethrough: true, + emoji: true, + ghCodeBlocks: true, + tasklists: true, + ghMentions: true, + smoothLivePreview: true, + simplifiedAutoLink: true, + parseImgDimensions: true, + openLinksInNewWindow: true, +}); + +const JOV_HELP_URL = "/jovimetrix/doc"; + +async function load_help(name, custom_data) { + // overwrite + if (custom_data) { + CACHE_DOCUMENTATION[name] = documentationConverter.makeHtml(custom_data); + } + + if (name in CACHE_DOCUMENTATION) { + return CACHE_DOCUMENTATION[name]; + } + + const url = `${JOV_HELP_URL}/${name}`; + // Check if data is already cached + const result = fetch(url, + { cache: "no-store" } + ) + .then(response => { + if (!response.ok) { + return `Failed to load documentation: ${name}\n\n${response}` + } + return response.text(); + }) + .then(data => { + // Cache the fetched data + if (data.startsWith("unknown")) { + data = ` +
+

${data}

+

SELECT A NODE TO SEE HELP

+

JOVIMETRIX πŸ”ΊπŸŸ©πŸ”΅ NODES ALL HAVE HELP

+
+ `; + }; + CACHE_DOCUMENTATION[name] = data; + return CACHE_DOCUMENTATION[name]; + }) + .catch(error => { + console.error('Error:', error); + return `Failed to load documentation: ${name}\n\n${error}` + }); + return result; +} + +app.registerExtension({ + name: "jovimetrix.help", + async beforeRegisterNodeDef(nodeType, nodeData) { + if (!nodeData?.category?.startsWith("JOVIMETRIX")) { + return; + } + // if the old menu is present.... + //if(new_menu == "Disabled" || !app.extensionManager) { + let opts = { icon_size: 14, icon_margin: 3 } + const iconSize = opts.icon_size ? opts.icon_size : 14; + const iconMargin = opts.icon_margin ? opts.icon_margin : 3; + let docElement = null; + let contentWrapper = null; + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = async function () { + const me = onNodeCreated?.apply(this); + this.offsetX = 0; + this.offsetY = 0; + this.show_doc = false; + this.onRemoved = function () { + nodeCleanup(this); + if (docElement) { + docElement.remove(); + docElement = null; + } + if (contentWrapper) { + contentWrapper.remove(); + contentWrapper = null; + } + } + return me; + } + + const onDrawForeground = nodeType.prototype.onDrawForeground; + nodeType.prototype.onDrawForeground = async function (ctx) { + const me = onDrawForeground?.apply?.(this, arguments); + if (this.flags.collapsed) return me; + + const x = this.size[0] - iconSize - iconMargin + if (this.show_doc && docElement === null) { + + docElement = document.createElement('div') + docElement.classList.add('jov-panel-doc-popup'); + + const widget_tooltip = (this.widgets || []) + .find(widget => widget.type === 'JTOOLTIP'); + + if (widget_tooltip) { + const tips = widget_tooltip.options.default || {}; + const url_name = `jovimetrix/${tips['*']}`; + docElement.innerHTML = await load_help(url_name); + } + + // resize handle + 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 = '5px solid transparent'; + resizeHandle.style.borderLeft = '5px solid transparent'; + resizeHandle.style.borderBottom = `5px solid ${borderColor}`; + resizeHandle.style.borderRight = `5px 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.helpClose.signal }, + ); + + const buttons = document.createElement('div'); + // wiki page + const wikiButton = document.createElement('div'); + wikiButton.textContent = '🌐'; + wikiButton.style.position = 'absolute'; + wikiButton.style.top = '6px'; + wikiButton.style.right = '22px'; + wikiButton.style.cursor = 'pointer'; + wikiButton.style.padding = '4px'; + wikiButton.style.font = 'bold 14px monospace'; + wikiButton.addEventListener('mousedown', (e) => { + e.stopPropagation(); + const widget_tooltip = (this.widgets || []) + .find(widget => widget.type === 'JTOOLTIP'); + if (widget_tooltip) { + const tips = widget_tooltip.options.default || {}; + const url = tips['_']; + if (url !== undefined) { + window.open(`${JOV_WEBWIKI_URL}/${url}`, '_blank'); + } + wikiButton.tooltip = `WIKI PAGE: ${url}` + } + }); + buttons.appendChild(wikiButton); + + // close button + const closeButton = document.createElement('div'); + closeButton.textContent = '❌'; + closeButton.style.position = 'absolute'; + closeButton.style.top = '6px'; + closeButton.style.right = '4px'; + closeButton.style.cursor = 'pointer'; + closeButton.style.padding = '4px'; + closeButton.style.font = 'bold 14px monospace'; + closeButton.addEventListener('mousedown', (e) => { + e.stopPropagation(); + this.show_doc = !this.show_doc + if (contentWrapper) { + contentWrapper.remove() + contentWrapper = null + } + docElement.parentNode.removeChild(docElement) + docElement = null + }, + { signal: this.helpClose.signal }, + ); + buttons.appendChild(closeButton); + docElement.appendChild(buttons); + + 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.helpClose.signal }, + ); + document.addEventListener('mouseup', function () { + isResizing = false + }, + { signal: this.helpClose.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 styleObject = { + transformOrigin: '0 0', + transform: scale, + left: `${transform.a + transform.e}px`, + top: `${transform.d + transform.f}px`, + }; + Object.assign(docElement.style, styleObject); + } + + ctx.save(); + ctx.translate(x-3, -LiteGraph.NODE_TITLE_HEIGHT * 0.65); + ctx.scale(iconSize / 32, iconSize / 32); + ctx.font = `bold ${LiteGraph.NODE_TITLE_HEIGHT * 1.35}px monospace`; + ctx.fillText('πŸ›ˆ', 0, 24); // ℹ️ + ctx.restore() + return me; + } + + // ? clicked + const mouseDown = nodeType.prototype.onMouseDown + nodeType.prototype.onMouseDown = function (e, localPos) { + 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; + } + if (this.show_doc) { + this.helpClose = new AbortController() + } else { + this.helpClose.abort() + } + // Dispatch a custom event with the node name + const data = { + class: this.getNickname() || "jovimetrix", + name: this.type + } + const event = new CustomEvent('jovimetrixHelpRequested', { detail: data }); + jovimetrixEvents.dispatchEvent(event); + return true; + } + return r; + } + //} + } +}) diff --git a/web/util/util_jov.js b/web/util/util_jov.js index 6eb526d..67fe247 100644 --- a/web/util/util_jov.js +++ b/web/util/util_jov.js @@ -84,8 +84,8 @@ export function widgetHookAB(node, control_key) { return; } - widgetHookControl(node, control_key, AA); - widgetHookControl(node, control_key, BB); + widgetHookControl(node, AA, control_key); + widgetHookControl(node, BB, control_key); widgetOutputHookType(node, control_key); setTimeout(() => { combo.callback(); }, 5); @@ -97,6 +97,7 @@ export function widgetHookAB(node, control_key) { */ export function widgetHookControl(node, control_key, target, matchFloatSize=false) { + /* const initializeTrack = (widget) => { const track = {}; for (let i = 0; i < 4; i++) { @@ -105,19 +106,7 @@ export function widgetHookControl(node, control_key, target, matchFloatSize=fals Object.assign(track, widget.value); return track; }; - - const setCallback = (widget, trackKey) => { - widget.options.menu = false; - widget.callback = () => { - if (widget.type === "toggle") { - trackKey[0] = widget.value ? 1 : 0; - } else { - Object.keys(widget.value).forEach((key) => { - trackKey[key] = widget.value[key]; - }); - } - }; - }; + */ const { widgets } = node; const combo = widgets.find(w => w.name === control_key); @@ -127,7 +116,7 @@ export function widgetHookControl(node, control_key, target, matchFloatSize=fals } const data = { - track_xyzw: initializeTrack(target), + track_xyzw: target.options?.default, //initializeTrack(target), target, combo }; @@ -153,6 +142,17 @@ export function widgetHookControl(node, control_key, target, matchFloatSize=fals nodeFitHeight(node); return me; } - setCallback(target, data.track_xyzw); + + target.options.menu = false; + target.callback = () => { + if (target.type === "toggle") { + data.track_xyzw[0] = target.value ? 1 : 0; + } else { + Object.keys(target.value).forEach((key) => { + data.track_xyzw[key] = target.value[key]; + }); + } + }; + return data; }