/* globals LiteGraph */ import {app} from "../../scripts/app.js" // import {api} from "../../scripts/api.js"; import {PresentationDetachInputs} from "./PresentationDetachInputs.js"; import {PresentationSettings} from "./PresentationSettings.js"; class WorkflowToTable { static presentationNodes = {}; static presentationPrimitiveNodes = { "PresentationDropDown": true, "Note": true, "MarkdownNote": true, }; static presentationInputNodes = { "PresentationFile": true, "PresentationAudio": true, "PresentationVideo": true, "PresentationImage": true, "PresentationSeed": true, "PresentationAddQueue": true, }; static presentationOutputNodes = { "PreviewImage": true, "SaveImage": true, "PreviewAudio": true, "SaveAudio": true, "SaveVideo": true, "VHS_VideoCombine": true, }; static presentationMiscNodes = { "PresentationProgress": true, "PresentationComponent": true, "PresentationAdvancedSection": true, } static ignoreNodes = { "PresentationTab": true }; static makePresentationNodes() { Object.assign( WorkflowToTable.presentationNodes, WorkflowToTable.presentationPrimitiveNodes, WorkflowToTable.presentationInputNodes, WorkflowToTable.presentationOutputNodes, WorkflowToTable.presentationMiscNodes, ); } findGroupOfNode(node, groups) { const x = node.pos[0] + node.size[0] / 2; const y = node.pos[1] + node.size[1] / 2; for (const group of groups) { const isInside = LiteGraph.isInsideRectangle( x, y, group.bounding[0], group.bounding[1], group.bounding[2], group.bounding[3] ); if (isInside) { return group; } } return null; } getBoxFromNode(node) { const left = node.pos[0]; const right = left + node.size[0]; const top = node.pos[1]; const bottom = top + node.size[1]; return {left, top, right, bottom}; } getHorzNodeGaps(nodes) { return nodes.map((node) => { const nodeBox = this.getBoxFromNode(node); return { left:nodeBox.top, right:nodeBox.bottom, top:nodeBox.left, bottom:nodeBox.right, }; }); } getVertNodeGaps(nodes) { return nodes.map((node) => { const nodeBox = this.getBoxFromNode(node); return { left:nodeBox.left, right:nodeBox.right, top:nodeBox.top, bottom:nodeBox.bottom, }; }); } findGaps(nodeGaps) { const vertGaps = nodeGaps.map((nodeBox) => { let closestBelow = Number.MAX_VALUE; for (const nBox of nodeGaps) { if ( (nodeBox.left <= nBox.left && nBox.left < nodeBox.right) || (nodeBox.left <= nBox.right && nBox.right < nodeBox.right) || (nBox.left < nodeBox.left && nodeBox.right < nBox.right) ) { if (nBox.top < closestBelow && nBox.top > nodeBox.bottom) { closestBelow = nBox.top; } } } return [nodeBox.bottom, closestBelow]; }); for (const gap of vertGaps) { for (const thinGap of vertGaps) { if (gap[0] < thinGap[0] && thinGap[0] < gap[1]) { gap[0] = thinGap[0]; } if (gap[0] < thinGap[1] && thinGap[1] < gap[1]) { gap[1] = thinGap[1]; } } } const vertGapsHash = vertGaps.reduce((a, gap) => { a[`${gap[0]},${gap[1]}`]=true; return a; }, {}); const finalVertGaps = Object.keys(vertGapsHash).map( (gapStr) => gapStr.split(',') ); finalVertGaps.sort((a,b) => { return a[0] - b[0]; }); return finalVertGaps; } findNthGap(pos, vertGaps) { let row = 0; for (const gap of vertGaps) { if (pos < gap[1]) { break; } ++row; } return row; } mapRowNumbers(nodes) { const nodeInfos = {} const rows = []; const horzGaps = this.findGaps(this.getHorzNodeGaps(nodes)); const vertGaps = this.findGaps(this.getVertNodeGaps(nodes)); for (const node of nodes) { const info = {node}; nodeInfos[node.id] = info; const nodeBox = this.getBoxFromNode(node); const topRow = this.findNthGap(nodeBox.top, vertGaps); const bottomRow = this.findNthGap(nodeBox.bottom, vertGaps); const leftCol = this.findNthGap(nodeBox.left, horzGaps); const rightCol = this.findNthGap(nodeBox.right, horzGaps); let rowArr = rows[topRow]; if (!rowArr) { rowArr = rows[topRow] = []; } info.row = topRow; info.rowSpan = (bottomRow - topRow) + 1; info.col = leftCol; info.colSpan = (rightCol - leftCol) + 1; rowArr[info.col] = info; } return { nodeInfos, rows, horzGaps, vertGaps }; } makeTable(nodes) { const {rows, horzGaps, vertGaps} = this.mapRowNumbers(nodes); const tableRows = []; for (const rowNodeInfos of rows) { const tableRow = []; for (const nodeInfo of rowNodeInfos) { tableRows.push(nodeInfo?.node); } tableRows.push(tableRow); } return {tableRows, rows, horzGaps, vertGaps}; } static getPresentationPrice(presentationTab) { return Number.parseInt(presentationTab?.widgets_values?.[1]); } static getPresentationTabName(presentationTab) { return presentationTab?.widgets_values?.[0]; } getGroupInfos(workflow) { const groupInfos = { }; const presentationTabs = workflow.nodes.filter((node) => { return (WorkflowToTable.ignoreNodes[node.type]); }); for (const presentationNodeId in presentationTabs) { const presentationTab = presentationTabs[presentationNodeId]; const group = this.findGroupOfNode(presentationTab, workflow.groups); if (group) { groupInfos[group.id] = { nodes: [], group, presentationTab, }; } } for (const node of workflow.nodes) { // if (node && WorkflowToTable.okNodes[node.type]) { if (node && !WorkflowToTable.ignoreNodes[node.type]) { const group = this.findGroupOfNode(node, workflow.groups); if (group) { const groupInfo = groupInfos[group.id]; if (groupInfo) { groupInfo.nodes.push(node); } } } } // console.log('groupInfos',groupInfos); return groupInfos; } } class ComfyToVueType { constructor() { const numberType = { type:'Number', html:(widget_id, v_input_name, input_name) => { return { html:``, } }, scriptLine:(widget_id, v_input_name, input_name) => { return `const size_${input_name} = props.size || props.value0?.toString()?.length`; }, }; const stringType = { type:'String', html:(widget_id, v_input_name, input_name) => { return { html:``, }; }, }; const textBoxType = { type:'String', html:(widget_id, v_input_name, input_name) => { return { html:``, }; }, scriptLine:(widget_id, v_input_name, input_name) => { if (input_name === 'value') { return `const r_${input_name} = ref(props.${v_input_name});\n`; } return ''; }, }; const toggleType = { type:'Boolean', html:(widget_id, v_input_name, input_name) => { return { html:``, }; }, }; const comboType = { type:'String', html:(widget_upto, v_input_name, input_name /*, widget */) => { const optionsId = `options_${input_name}`; return { html:``, }; }, }; this.comfyToVueTypeMap = { 'number':numberType, 'toggle':toggleType, 'text':stringType, 'customtext':textBoxType, 'combo':comboType, }; } comfyToVueType(type) { return this.comfyToVueTypeMap[type]; } } class PresentationTab { constructor() { this.onChangeTimeoutId = null; this.prevWorkflow = null; this.presentationNodes = {}; this.prevPromptStr = null; this.comfyToVueType = new ComfyToVueType(); // this.debug = true; } static getNodeClass() { return 'PresentationTab'; } getWidgetFromLinkId(graph, destLinkId) { const link = graph.links[destLinkId]; const destNodeId = link.target_id; const destNodeSlot = link.target_slot; const destNode = graph.getNodeById(destNodeId); if (!destNode) { return null; } const name = destNode.inputs[destNodeSlot]?.name; for (const widget of destNode.widgets) { if (widget.name === name) { return widget; } } return null; } fixTypeName(name) { return name.replace(/[^a-zA-Z0-9_]/g,''); } getCamelName(name) { return name.substring(0,1).toUpperCase() + name.substring(1).toLowerCase(); } getGraphNodeInfo(graphNode) { const inputsByName = {}; for (const input of graphNode.inputs) { inputsByName[input.name] = input; } const widgetsByName = {}; if (graphNode.widgets) { for (const widget of graphNode.widgets) { widgetsByName[widget.name] = widget; } } return { graphNode, inputsByName, widgetsByName, }; } makeComponent(workflowNode, graphNodeInfo /*, componentName*/) { const graphNode = graphNodeInfo.graphNode; const okWidgets = []; for (const widget of graphNode.widgets) { const input = graphNodeInfo.inputsByName[widget.name]; if (!input) { console.log('widget not found in inputs', widget.name, workflowNode, graphNode); continue; } if (input.link) { continue; } const vueType = this.comfyToVueType.comfyToVueType(widget.type); if (!vueType) { continue; } okWidgets.push({widget, vueType}); } const scripts = []; const scriptLines = []; const htmlInputs = []; let widget_upto = -1; const oneWidget = (okWidgets.length === 1); for (const widgetObj of okWidgets) { const {widget, vueType}=widgetObj; ++widget_upto; const input_name = widget.name; const title = oneWidget ? '{{title}}' : input_name ; const safe_input_name = input_name.replace(/[^a-zA-Z0-9_]/g,'') const v_input_name = `v_${safe_input_name}`; scripts.push(`"${v_input_name}": { type: ${vueType.type} }`); const options_input_name = `options_${safe_input_name}`; scripts.push(`"${options_input_name}": { type: Array }`); if(vueType.scriptLine) { scriptLines.push(vueType.scriptLine(widget_upto, v_input_name, safe_input_name, widget)); } const htmlInput = vueType.html(widget_upto, v_input_name, safe_input_name, widget); if (oneWidget) { htmlInputs.push(htmlInput.html); } else { htmlInputs.push(`