Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c275a56806 | ||
|
|
82c3c7addd | ||
|
|
561805a417 | ||
|
|
7bb4324365 | ||
|
|
0608653d35 | ||
|
|
fa3472cdc5 | ||
|
|
7d553b6fcf | ||
|
|
677627630e | ||
|
|
9f23172b22 | ||
|
|
14ceaa472d | ||
|
|
1188b9d3bc |
@@ -149,3 +149,23 @@ pip3 install -r requirements.txt
|
||||
- 音频播放节点:带可视化、支持多音轨、可配置音轨音量
|
||||
- vector https://github.com/GeorgLegato/stable-diffusion-webui-vectorstudio
|
||||
|
||||
|
||||
<picture>
|
||||
<source
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="
|
||||
https://api.star-history.com/svg?repos=shadowcz007/comfyui-mixlab-nodes&type=Date&theme=dark
|
||||
"
|
||||
/>
|
||||
<source
|
||||
media="(prefers-color-scheme: light)"
|
||||
srcset="
|
||||
https://api.star-history.com/svg?repos=shadowcz007/comfyui-mixlab-nodes&type=Date
|
||||
"
|
||||
/>
|
||||
<img
|
||||
alt="Star History Chart"
|
||||
src="https://api.star-history.com/svg?repos=shadowcz007/comfyui-mixlab-nodes&type=Date"
|
||||
/>
|
||||
</picture>
|
||||
|
||||
|
||||
+30
@@ -169,6 +169,22 @@ def save_workflow_json(data):
|
||||
return workflow_path
|
||||
|
||||
|
||||
def get_nodes_map():
|
||||
# print("#####path::", current_path)
|
||||
data_path=os.path.join(current_path, "data")
|
||||
print('data_path: ',data_path)
|
||||
# if not os.path.exists(data_path):
|
||||
# # 使用mkdir()方法创建新目录
|
||||
# os.mkdir(data_path)
|
||||
json_data={}
|
||||
nodes_map=os.path.join(current_path, "data/extension-node-map.json")
|
||||
if os.path.exists(nodes_map):
|
||||
with open(nodes_map) as json_file:
|
||||
json_data = json.load(json_file)
|
||||
|
||||
return json_data
|
||||
|
||||
|
||||
# 保存原始的 get 方法
|
||||
_original_request = aiohttp.ClientSession._request
|
||||
|
||||
@@ -259,6 +275,20 @@ async def mixlab_workflow_hander(request):
|
||||
|
||||
return web.json_response(result)
|
||||
|
||||
@routes.post('/mixlab/nodes_map')
|
||||
async def nodes_map_hander(request):
|
||||
data = await request.json()
|
||||
result={}
|
||||
try:
|
||||
result={
|
||||
'data':get_nodes_map(),
|
||||
'status':'success',
|
||||
}
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
return web.json_response(result)
|
||||
|
||||
def new_add_routes(self):
|
||||
import nodes
|
||||
self.app.add_routes(routes)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+10
-11
@@ -2,32 +2,31 @@ import os
|
||||
|
||||
# FONT_PATH= os.path.abspath(os.path.join(os.path.dirname(__file__),'../assets/王汉宗颜楷体繁.ttf'))
|
||||
|
||||
|
||||
|
||||
import matplotlib.font_manager as fm
|
||||
|
||||
def get_font_files(directory):
|
||||
|
||||
font_files = {}
|
||||
|
||||
# 从指定目录加载字体
|
||||
for file in os.listdir(directory):
|
||||
if file.endswith('.ttf') or file.endswith('.otf'):
|
||||
font_name = os.path.splitext(file)[0]
|
||||
font_path = os.path.join(directory, file)
|
||||
font_files[font_name] = os.path.abspath(font_path)
|
||||
|
||||
# 尝试获取系统字体
|
||||
try:
|
||||
import sys
|
||||
if sys.platform != "darwin":
|
||||
import matplotlib.font_manager as fm
|
||||
font_paths = fm.findSystemFonts()
|
||||
for path in font_paths:
|
||||
font_paths = fm.findSystemFonts()
|
||||
for path in font_paths:
|
||||
try:
|
||||
font_prop = fm.FontProperties(fname=path)
|
||||
font_name = font_prop.get_name()
|
||||
font_files[font_name] = path
|
||||
except ValueError:
|
||||
print("findSystemFonts error")
|
||||
except Exception as e:
|
||||
print(f"Error processing font {path}: {e}")
|
||||
except Exception as e:
|
||||
print(f"Error finding system fonts: {e}")
|
||||
|
||||
|
||||
return font_files
|
||||
|
||||
r_directory = os.path.join(os.path.dirname(__file__), '../assets/')
|
||||
|
||||
@@ -3,7 +3,7 @@ import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
async function uploadImage (blob,fileType='.svg') {
|
||||
async function uploadImage (blob, fileType = '.svg') {
|
||||
// const blob = await (await fetch(src)).blob();
|
||||
const body = new FormData()
|
||||
body.append('image', new File([blob], new Date().getTime() + fileType))
|
||||
@@ -25,9 +25,8 @@ async function uploadImage (blob,fileType='.svg') {
|
||||
return src
|
||||
}
|
||||
|
||||
function base64ToBlobFromURL(base64URL, contentType) {
|
||||
return fetch(base64URL)
|
||||
.then(response => response.blob());
|
||||
function base64ToBlobFromURL (base64URL, contentType) {
|
||||
return fetch(base64URL).then(response => response.blob())
|
||||
}
|
||||
|
||||
function getContentTypeFromBase64 (base64Data) {
|
||||
@@ -126,6 +125,7 @@ const parseImage = url => {
|
||||
}
|
||||
|
||||
const parseSvg = async svgContent => {
|
||||
let scale = 2
|
||||
// 创建一个临时的DOM元素来解析SVG
|
||||
const tempContainer = document.createElement('div')
|
||||
tempContainer.innerHTML = svgContent
|
||||
@@ -135,17 +135,18 @@ const parseSvg = async svgContent => {
|
||||
if (!svgElement) return
|
||||
// 获取SVG中 rect元素
|
||||
var rectElements = svgElement?.querySelectorAll('rect') || []
|
||||
|
||||
// console.log(rectElements,svgElement)
|
||||
// 定义一个数组来存储处理后的数据
|
||||
var data = []
|
||||
|
||||
Array.from(rectElements, (rectElement, i) => {
|
||||
// 获取rect元素的属性值
|
||||
var x = rectElement.getAttribute('x')
|
||||
var y = rectElement.getAttribute('y')
|
||||
var width = rectElement.getAttribute('width')
|
||||
var height = rectElement.getAttribute('height')
|
||||
if (x != undefined && y != undefined) {
|
||||
var x = ~~(rectElement.getAttribute('x') || 0)
|
||||
var y = ~~(rectElement.getAttribute('y') || 0)
|
||||
var width = ~~rectElement.getAttribute('width')
|
||||
var height = ~~rectElement.getAttribute('height')
|
||||
// console.log('rectElements',rectElement,x,y,width,height)
|
||||
if (x != undefined && y != undefined && width && height) {
|
||||
// 创建一个新的canvas元素
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = width
|
||||
@@ -171,7 +172,8 @@ const parseSvg = async svgContent => {
|
||||
scale_option: 'width',
|
||||
image: base64,
|
||||
mask: base64,
|
||||
type: 'base64'
|
||||
type: 'base64',
|
||||
_t: 'rect'
|
||||
}
|
||||
|
||||
// 将处理后的数据添加到数组中
|
||||
@@ -181,6 +183,15 @@ const parseSvg = async svgContent => {
|
||||
|
||||
var svgWidth = svgElement.getAttribute('width')
|
||||
var svgHeight = svgElement.getAttribute('height')
|
||||
|
||||
if (!(svgWidth && svgHeight)) {
|
||||
// viewBox
|
||||
let viewBox = svgElement.viewBox.baseVal
|
||||
|
||||
svgWidth = viewBox.width
|
||||
svgHeight = viewBox.height
|
||||
}
|
||||
|
||||
// 创建一个新的canvas元素
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = svgWidth
|
||||
@@ -207,12 +218,13 @@ const parseSvg = async svgContent => {
|
||||
scale_option: 'width',
|
||||
image: base64,
|
||||
mask: base64,
|
||||
type: 'base64'
|
||||
type: 'base64',
|
||||
_t: 'canvas'
|
||||
}
|
||||
data.push(rectData)
|
||||
|
||||
// 打印处理后的数据
|
||||
// console.log({ data, image: base64, svgElement })
|
||||
console.log('layers', { data, image: base64, svgElement })
|
||||
return { data, image: base64, svgElement }
|
||||
}
|
||||
|
||||
@@ -327,6 +339,12 @@ app.registerExtension({
|
||||
|
||||
svgContainer.innerHTML = ''
|
||||
svgContainer.appendChild(svgElement)
|
||||
let h = ~~getComputedStyle(svgElement).height.replace('px', '')
|
||||
if (that.size && that.size[1] < h) {
|
||||
that.setSize([that.size[0], that.size[1] + h])
|
||||
app.canvas.draw(true, true)
|
||||
}
|
||||
// console.log(that.size,~~getComputedStyle(svgElement).height.replace('px',''))
|
||||
|
||||
uploadWidget.value = await uploadWidget.serializeValue()
|
||||
}
|
||||
@@ -509,7 +527,9 @@ app.registerExtension({
|
||||
<div><button class="capture">Capture</button></div>
|
||||
</div></model-viewer>`
|
||||
|
||||
preview.innerHTML = html
|
||||
preview.innerHTML = html;
|
||||
that.setSize([that.size[0],that.size[1]+300])
|
||||
app.canvas.draw(true, true)
|
||||
|
||||
const modelViewerVariants = preview.querySelector('model-viewer')
|
||||
const select = preview.querySelector('.variant')
|
||||
@@ -540,10 +560,10 @@ app.registerExtension({
|
||||
|
||||
const contentType = getContentTypeFromBase64(base64Data)
|
||||
|
||||
const blob =await base64ToBlobFromURL(base64Data, contentType)
|
||||
const blob = await base64ToBlobFromURL(base64Data, contentType)
|
||||
|
||||
// const fileBlob = new Blob([e.target.result], { type: file.type });
|
||||
let url = await uploadImage(blob,'.png')
|
||||
let url = await uploadImage(blob, '.png')
|
||||
console.log(url)
|
||||
|
||||
let dd = getLocalData(key)
|
||||
@@ -563,13 +583,25 @@ app.registerExtension({
|
||||
|
||||
let preview = document.createElement('div')
|
||||
preview.className = 'preview'
|
||||
preview.style = `background:#eee;margin-top: 12px;`
|
||||
preview.style = `margin-top: 12px;display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;`
|
||||
|
||||
let upload = inputDiv('_mixlab_3d_image', '3D Model', preview)
|
||||
|
||||
widget.div.appendChild(upload)
|
||||
widget.div.appendChild(preview)
|
||||
this.addCustomWidget(widget)
|
||||
this.addCustomWidget(widget);
|
||||
|
||||
const onResize = this.onResize;
|
||||
this.onResize = function () {
|
||||
let m=preview.querySelector('model-viewer')
|
||||
m.style.width=`${this.size[0]-24}px`
|
||||
m.style.height=`${this.size[1]-48}px`
|
||||
// console.log(this.size,preview)
|
||||
return onResize?.apply(this, arguments);
|
||||
};
|
||||
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
@@ -608,7 +640,7 @@ app.registerExtension({
|
||||
let url = dd[id]
|
||||
// let base64 = await parseImage(url)
|
||||
|
||||
widget.div.querySelector('.preview').innerHTML = `<img src="${url}"/>`
|
||||
widget.div.querySelector('.preview').innerHTML = `<img src="${url}"/>`
|
||||
|
||||
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
|
||||
uploadWidget.value = await uploadWidget.serializeValue()
|
||||
|
||||
@@ -63,17 +63,18 @@ const parseSvg = async svgContent => {
|
||||
if (!svgElement) return
|
||||
// 获取SVG中 rect元素
|
||||
var rectElements = svgElement?.querySelectorAll('rect') || []
|
||||
|
||||
// console.log(rectElements,svgElement)
|
||||
// 定义一个数组来存储处理后的数据
|
||||
var data = []
|
||||
|
||||
Array.from(rectElements, (rectElement, i) => {
|
||||
// 获取rect元素的属性值
|
||||
var x = rectElement.getAttribute('x')
|
||||
var y = rectElement.getAttribute('y')
|
||||
var width = rectElement.getAttribute('width')
|
||||
var height = rectElement.getAttribute('height')
|
||||
if (x != undefined && y != undefined) {
|
||||
var x = ~~(rectElement.getAttribute('x')||0);
|
||||
var y = ~~(rectElement.getAttribute('y')||0);
|
||||
var width = ~~rectElement.getAttribute('width')
|
||||
var height = ~~rectElement.getAttribute('height')
|
||||
// console.log('rectElements',rectElement,x,y,width,height)
|
||||
if (x != undefined && y != undefined&&width&&height) {
|
||||
// 创建一个新的canvas元素
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = width
|
||||
@@ -99,7 +100,8 @@ const parseSvg = async svgContent => {
|
||||
scale_option: 'width',
|
||||
image: base64,
|
||||
mask: base64,
|
||||
type: 'base64'
|
||||
type: 'base64',
|
||||
_t:'rect'
|
||||
}
|
||||
|
||||
// 将处理后的数据添加到数组中
|
||||
@@ -109,6 +111,15 @@ const parseSvg = async svgContent => {
|
||||
|
||||
var svgWidth = svgElement.getAttribute('width')
|
||||
var svgHeight = svgElement.getAttribute('height')
|
||||
|
||||
if (!(svgWidth && svgHeight)) {
|
||||
// viewBox
|
||||
let viewBox = svgElement.viewBox.baseVal
|
||||
|
||||
svgWidth =viewBox.width
|
||||
svgHeight =viewBox.height
|
||||
}
|
||||
|
||||
// 创建一个新的canvas元素
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = svgWidth
|
||||
@@ -135,12 +146,13 @@ const parseSvg = async svgContent => {
|
||||
scale_option: 'width',
|
||||
image: base64,
|
||||
mask: base64,
|
||||
type: 'base64'
|
||||
type: 'base64',
|
||||
_t:'canvas'
|
||||
}
|
||||
data.push(rectData)
|
||||
|
||||
// 打印处理后的数据
|
||||
console.log({ data, image: base64, svgElement })
|
||||
console.log('layers',{ data, image: base64, svgElement })
|
||||
return { data, image: base64, svgElement }
|
||||
}
|
||||
|
||||
@@ -199,11 +211,15 @@ app.registerExtension({
|
||||
|
||||
// 获取layers数据
|
||||
const getLayers = async () => {
|
||||
console.log('getLayers1',this.inputs.filter(ip => ip.name === 'layers'))
|
||||
let linkId = this.inputs.filter(ip => ip.name === 'layers')[0].link
|
||||
let nodeId = app.graph.links.filter(link => link.id == linkId)[0]
|
||||
?.origin_id
|
||||
let nodeId = app.graph.links?.filter(link => link.id == linkId)[0]
|
||||
?.origin_id;
|
||||
|
||||
nodeId = findNode(nodeId)
|
||||
if(nodeId){
|
||||
nodeId = findNode(nodeId)
|
||||
}
|
||||
|
||||
|
||||
// let node = app.graph._nodes_by_id[nodeId]
|
||||
// if (node?.type == 'Reroute') {
|
||||
@@ -218,8 +234,11 @@ app.registerExtension({
|
||||
if (d[nodeId]) {
|
||||
let url = d[nodeId]
|
||||
let dt = await fetch(url)
|
||||
|
||||
let svgStr = await dt.text()
|
||||
|
||||
const { data } = (await parseSvg(svgStr)) || {}
|
||||
console.log('fetch',data)
|
||||
return data
|
||||
} else {
|
||||
return []
|
||||
|
||||
@@ -890,6 +890,9 @@ app.registerExtension({
|
||||
})
|
||||
// console.log('widget.inputEl',widget.inputEl)
|
||||
|
||||
this.setSize([this.size[0],this.size[1]+450])
|
||||
app.canvas.draw(true, true)
|
||||
|
||||
/*
|
||||
Add the widget, make sure we clean up nicely, and we do not want to be serialized!
|
||||
*/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const closeIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/></svg>'
|
||||
+637
-31
@@ -1,19 +1,73 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
import {closeIcon} from './svg_icons.js'
|
||||
|
||||
function injectCSS(css) {
|
||||
function deepEqual (obj1, obj2) {
|
||||
if (typeof obj1 !== typeof obj2) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof obj1 !== 'object' || obj1 === null || obj2 === null) {
|
||||
return obj1 === obj2
|
||||
}
|
||||
|
||||
const keys1 = Object.keys(obj1)
|
||||
const keys2 = Object.keys(obj2)
|
||||
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let key of keys1) {
|
||||
if (!deepEqual(obj1[key], obj2[key])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async function get_nodes_map () {
|
||||
let api_host = `${window.location.hostname}:${window.location.port}`
|
||||
let api_base = ''
|
||||
let url = `${window.location.protocol}//${api_host}${api_base}`
|
||||
|
||||
const res = await fetch(`${url}/mixlab/nodes_map`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
data: 'json'
|
||||
})
|
||||
})
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
function loadCSS(url) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = url;
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
}
|
||||
|
||||
var cssURL = 'https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.0/github-markdown-light.min.css';
|
||||
loadCSS(cssURL);
|
||||
|
||||
function injectCSS (css) {
|
||||
// 检查页面中是否已经存在具有相同内容的style标签
|
||||
const existingStyle = document.querySelector('style');
|
||||
const existingStyle = document.querySelector('style')
|
||||
if (existingStyle && existingStyle.textContent === css) {
|
||||
return; // 如果已经存在相同的样式,则不进行注入
|
||||
return // 如果已经存在相同的样式,则不进行注入
|
||||
}
|
||||
|
||||
// 创建一个新的style标签,并将CSS内容注入其中
|
||||
const style = document.createElement('style');
|
||||
style.textContent = css;
|
||||
const style = document.createElement('style')
|
||||
style.textContent = css
|
||||
|
||||
// 将style标签插入到页面的head元素中
|
||||
const head = document.querySelector('head');
|
||||
head.appendChild(style);
|
||||
const head = document.querySelector('head')
|
||||
head.appendChild(style)
|
||||
}
|
||||
|
||||
injectCSS(`::-webkit-scrollbar {
|
||||
@@ -39,8 +93,7 @@ injectCSS(`::-webkit-scrollbar {
|
||||
animation-name: loading_mixlab;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
}`);
|
||||
|
||||
}`)
|
||||
|
||||
async function getCustomnodeMappings (mode = 'url') {
|
||||
// mode = "local";
|
||||
@@ -49,17 +102,38 @@ async function getCustomnodeMappings (mode = 'url') {
|
||||
let url = `${window.location.protocol}//${api_host}${api_base}`
|
||||
|
||||
let nodes = {}
|
||||
try {
|
||||
const response = await fetch(`${url}/customnode/getmappings?mode=${mode}`)
|
||||
const data = await response.json()
|
||||
for (let url in data) {
|
||||
let n = data[url]
|
||||
for (let node of n[0]) {
|
||||
// if(node=='CLIPSeg')console.log('#CLIPSeg',n)
|
||||
nodes[node] = { url, title: n[1].title_aux }
|
||||
}
|
||||
|
||||
const data = (await get_nodes_map()).data
|
||||
|
||||
for (let url in data) {
|
||||
let n = data[url]
|
||||
for (let node of n[0]) {
|
||||
// if(node=='CLIPSeg')console.log('#CLIPSeg',n)
|
||||
nodes[node] = { url, title: n[1].title_aux }
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
// try {
|
||||
// const response = await fetch(`${url}/customnode/getmappings?mode=${mode}`)
|
||||
// const data = await response.json()
|
||||
// for (let url in data) {
|
||||
// let n = data[url]
|
||||
// for (let node of n[0]) {
|
||||
// // if(node=='CLIPSeg')console.log('#CLIPSeg',n)
|
||||
// nodes[node] = { url, title: n[1].title_aux }
|
||||
// }
|
||||
// }
|
||||
// } catch (error) {
|
||||
// const data = (await get_nodes_map()).data
|
||||
|
||||
// for (let url in data) {
|
||||
// let n = data[url]
|
||||
// for (let node of n[0]) {
|
||||
// // if(node=='CLIPSeg')console.log('#CLIPSeg',n)
|
||||
// nodes[node] = { url, title: n[1].title_aux }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
return nodes
|
||||
}
|
||||
@@ -97,14 +171,51 @@ const missingNodeGithub = (missingNodeTypes, nodesMap) => {
|
||||
})
|
||||
}
|
||||
|
||||
let nodesMap
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
app.showMissingNodesError = async function (
|
||||
missingNodeTypes,
|
||||
hasAddedNodes = true
|
||||
) {
|
||||
nodesMap =
|
||||
nodesMap && Object.keys(nodesMap).length > 0
|
||||
? nodesMap
|
||||
: await getCustomnodeMappings('url')
|
||||
|
||||
const nodesMap = await getCustomnodeMappings()
|
||||
console.log('#nodesMap', nodesMap)
|
||||
console.log('###MIXLAB', missingNodeTypes, hasAddedNodes)
|
||||
// console.log('#nodesMap', nodesMap)
|
||||
// console.log('###MIXLAB', missingNodeTypes, hasAddedNodes)
|
||||
this.ui.dialog.show(
|
||||
`When loading the graph, the following node types were not found: <ul>${missingNodeGithub(
|
||||
missingNodeTypes,
|
||||
@@ -120,12 +231,507 @@ app.showMissingNodesError = async function (
|
||||
})
|
||||
}
|
||||
|
||||
// app.ui.dialog.show = function (html) {
|
||||
// console.log('###MIXLAB', html)
|
||||
// if (typeof html === 'string') {
|
||||
// this.textElement.innerHTML = html
|
||||
// } else {
|
||||
// this.textElement.replaceChildren(html)
|
||||
// }
|
||||
// this.element.style.display = 'flex'
|
||||
// }
|
||||
// app.registerExtension({
|
||||
// name: 'Comfy.MDNote',
|
||||
// registerCustomNodes () {
|
||||
// class NoteNode {
|
||||
// // color = LGraphCanvas.node_colors.yellow.color
|
||||
// // bgcolor = LGraphCanvas.node_colors.yellow.bgcolor
|
||||
// // groupcolor = LGraphCanvas.node_colors.yellow.groupcolor
|
||||
// constructor () {
|
||||
// if (!this.properties) {
|
||||
// this.properties = {}
|
||||
// this.properties.text = ''
|
||||
// }
|
||||
// console.log('NoteNode1', this)
|
||||
|
||||
// const widget = {
|
||||
// type: 'div',
|
||||
// name: 'input_color',
|
||||
// draw (ctx, node, widget_width, y, widget_height) {
|
||||
// Object.assign(
|
||||
// this.div.style,
|
||||
// get_position_style(
|
||||
// ctx,
|
||||
// widget_width,
|
||||
// 44,
|
||||
// node.size[1]
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
// widget.div = $el('div', {});
|
||||
// widget.div.innerText='1111'
|
||||
|
||||
// document.body.appendChild(widget.div)
|
||||
|
||||
// this.addCustomWidget(widget)
|
||||
|
||||
// this.serialize_widgets = true
|
||||
// this.isVirtualNode = true
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Load default visibility
|
||||
|
||||
// LiteGraph.registerNodeType(
|
||||
// 'MDNote',
|
||||
// Object.assign(NoteNode, {
|
||||
// title_mode: LiteGraph.NORMAL_TITLE,
|
||||
// title: 'MDNote',
|
||||
// collapsable: true
|
||||
// })
|
||||
// )
|
||||
|
||||
// NoteNode.category = '♾️Mixlab/utils'
|
||||
// },
|
||||
|
||||
// })
|
||||
|
||||
async function fetchReadmeContent (url) {
|
||||
try {
|
||||
// var repo = 'owner/repo'; // 仓库的拥有者和名称
|
||||
var match = url.match(/github.com\/([^/]+\/[^/]+)/)
|
||||
var repo = match[1]
|
||||
var url = `https://api.github.com/repos/${repo}/readme`
|
||||
var response = await fetch(url)
|
||||
var data = await response.json()
|
||||
var readmeUrl = data.download_url
|
||||
|
||||
var readmeResponse = await fetch(readmeUrl)
|
||||
var content = await readmeResponse.text()
|
||||
// console.log(content) // 在控制台输出readme.md文件的内容
|
||||
|
||||
return content
|
||||
} catch (error) {
|
||||
console.log('获取readme.md文件信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
function createModal (url, markdown,title) {
|
||||
|
||||
// Create modal element
|
||||
var div =
|
||||
document.querySelector('#mix-modal') || document.createElement('div')
|
||||
div.id = 'mix-modal'
|
||||
div.innerHTML = ''
|
||||
div.style.cssText = `width: 100%;
|
||||
z-index: 9990;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
color: var(--descrip-text);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
`
|
||||
|
||||
var modal = document.createElement('div')
|
||||
|
||||
div.appendChild(modal)
|
||||
modal.classList.add("modal-body")
|
||||
// Set modal styles
|
||||
modal.style.cssText = `
|
||||
background: white;
|
||||
height: 80vh;
|
||||
position: fixed;
|
||||
overflow:hidden;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 9999;
|
||||
border-radius: 4px;
|
||||
box-shadow: 4px 4px 14px rgba(255,255,255,0.5);
|
||||
|
||||
`
|
||||
// Create modal content area
|
||||
var modalContent = document.createElement('div')
|
||||
modalContent.classList.add('modal-content')
|
||||
// Create modal header
|
||||
const headerElement = document.createElement('div')
|
||||
headerElement.classList.add('modal-header')
|
||||
headerElement.style.cssText = `
|
||||
display: flex;
|
||||
padding: 20px 24px 8px 24px;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const headTitleElement = document.createElement('a')
|
||||
headTitleElement.classList.add('header-title')
|
||||
headTitleElement.style.cssText=`
|
||||
color: var(--descrip-text);
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
`
|
||||
headTitleElement.onmouseenter = function(){
|
||||
headTitleElement.style.color = 'var(--comfy-menu-bg)'
|
||||
}
|
||||
headTitleElement.onmouseleave = function(){
|
||||
headTitleElement.style.color = 'var(--descrip-text)'
|
||||
}
|
||||
headTitleElement.textContent= title ||'';
|
||||
headTitleElement.href = url
|
||||
headTitleElement.target='_blank'
|
||||
const linkIcon = document.createElement('small')
|
||||
linkIcon.textContent = '🔗'
|
||||
headTitleElement.appendChild(linkIcon);
|
||||
headerElement.appendChild(headTitleElement)
|
||||
|
||||
|
||||
|
||||
// Create close button
|
||||
const closeButton = document.createElement('span')
|
||||
closeButton.classList.add('close')
|
||||
closeButton.innerHTML = closeIcon
|
||||
// Set close button styles
|
||||
closeButton.style.cssText = `
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
fill: var(--descrip-text);
|
||||
`
|
||||
closeButton.onmouseenter = function(){
|
||||
closeButton.style.fill = 'var(--comfy-menu-bg)';
|
||||
}
|
||||
closeButton.onmouseleave = function(){
|
||||
closeButton.style.fill = 'var(--descrip-text)';
|
||||
}
|
||||
|
||||
headerElement.appendChild(closeButton)
|
||||
|
||||
|
||||
// Click event to close the modal
|
||||
function closeMixModal(){
|
||||
div.style.display = 'none'
|
||||
window.removeEventListener('keydown',MixModalEscKeyEvent)
|
||||
}
|
||||
closeButton.onclick = function () {
|
||||
closeMixModal()
|
||||
}
|
||||
|
||||
// Set modal content area styles
|
||||
modalContent.style.cssText = `
|
||||
position: relative;
|
||||
padding: 0px;
|
||||
overflow: hidden scroll;;
|
||||
height: 100%;
|
||||
min-width:300px
|
||||
`
|
||||
|
||||
// Append close button to modal content area
|
||||
modal.appendChild(headerElement)
|
||||
|
||||
// Create element for displaying Markdown content
|
||||
var markdownContent = document.createElement('div')
|
||||
markdownContent.classList.add('markdown-content','markdown-body')
|
||||
markdownContent.style.cssText = `max-width: 50vw;padding: 0px 24px 100px 24px;`
|
||||
|
||||
showdown.setFlavor('github');
|
||||
var converter = new showdown.Converter()
|
||||
|
||||
var html = converter.makeHtml(markdown)
|
||||
|
||||
// Hide images in the markdown when they fail to load
|
||||
var regex = /<img[^>]+src="?([^"\s]+)"?[^>]*>/g
|
||||
html = html.replace(regex, function (match, src) {
|
||||
return `<img src="${src}" onerror="this.style.display='none'">`
|
||||
})
|
||||
|
||||
// Open links in a new tab or window
|
||||
html = html.replace(/<a/g, '<a target="_blank"')
|
||||
|
||||
// Fix href attribute to absolute path
|
||||
html = html.replace(
|
||||
/<a([^>]+href=["'])(?!https?:\/\/)([^"'>]+)/g,
|
||||
function (match, prefix, path) {
|
||||
var absolutePath = url + '/' + path
|
||||
return '<a' + prefix + absolutePath
|
||||
}
|
||||
)
|
||||
|
||||
markdownContent.innerHTML = html
|
||||
|
||||
// Append Markdown content element to modal content area
|
||||
modalContent.appendChild(markdownContent)
|
||||
|
||||
// Append modal content area to modal element
|
||||
modal.appendChild(modalContent)
|
||||
|
||||
const footerElement = document.createElement('div')
|
||||
footerElement.style.cssText=`
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: right;
|
||||
padding:10px;
|
||||
font-size:12px
|
||||
`
|
||||
|
||||
const footerText = document.createElement('a')
|
||||
footerText.href ="https://github.com/shadowcz007/comfyui-mixlab-nodes"
|
||||
footerText.innerText = "Support by Mixlab"
|
||||
footerText.style.cssText=`color:inherit`
|
||||
footerText.target = "_blank"
|
||||
footerText.onmouseenter=function(){
|
||||
footerText.style.color = 'var(--input-text)'
|
||||
}
|
||||
footerText.onmouseleave=function(){
|
||||
footerText.style.color = 'inherit'
|
||||
}
|
||||
|
||||
|
||||
footerText.onclick = function(e){
|
||||
e.stopPropagation()
|
||||
}
|
||||
footerElement.appendChild(footerText)
|
||||
|
||||
div.appendChild(footerElement)
|
||||
|
||||
|
||||
// Append modal element to the page
|
||||
if (!document.querySelector('#mix-modal')) {
|
||||
document.body.appendChild(div)
|
||||
}
|
||||
function MixModalEscKeyEvent(event){
|
||||
if(event.key == "Escape"){
|
||||
closeMixModal()
|
||||
}
|
||||
}
|
||||
window.removeEventListener('keydown',MixModalEscKeyEvent)
|
||||
window.addEventListener('keydown',MixModalEscKeyEvent)
|
||||
|
||||
const bgElement = document.createElement('div')
|
||||
bgElement.classList.add('mix-modal-bg')
|
||||
bgElement.style.cssText = `
|
||||
width:100%;
|
||||
height:100%;
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
`
|
||||
bgElement.onclick = function( ){
|
||||
closeMixModal()
|
||||
}
|
||||
|
||||
div.appendChild(bgElement)
|
||||
|
||||
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Comfy.Mixlab.ui',
|
||||
init () {
|
||||
LGraphCanvas.prototype.helpAboutNode = async function (node) {
|
||||
nodesMap =
|
||||
nodesMap && Object.keys(nodesMap).length > 0
|
||||
? nodesMap
|
||||
: await getCustomnodeMappings('url')
|
||||
|
||||
console.log('node & node map', node, nodesMap, nodesMap[node.type])
|
||||
let repo = nodesMap[node.type]
|
||||
if (repo) {
|
||||
let markdown = await fetchReadmeContent(repo.url)
|
||||
createModal(repo.url,markdown,repo.title)
|
||||
}
|
||||
}
|
||||
|
||||
const getNodeMenuOptions = LGraphCanvas.prototype.getNodeMenuOptions // store the existing method
|
||||
LGraphCanvas.prototype.getNodeMenuOptions = function (node) {
|
||||
// replace it
|
||||
const options = getNodeMenuOptions.apply(this, arguments) // start by calling the stored one
|
||||
node.setDirtyCanvas(true, true) // force a redraw of (foreground, background)
|
||||
// options.splice(
|
||||
// options.length - 1,
|
||||
// 0, // splice a new option in at the end
|
||||
// {
|
||||
// content: '♾️Mixlab', // with a name
|
||||
// callback: () => {
|
||||
// LGraphCanvas.prototype.helpAboutNode(node)
|
||||
// } // and the callback
|
||||
// },
|
||||
// null // a divider
|
||||
// )
|
||||
return [
|
||||
{
|
||||
content: 'Help ♾️Mixlab', // with a name
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype.helpAboutNode(node)
|
||||
} // and the callback
|
||||
},
|
||||
null,
|
||||
...options
|
||||
] // and return the options
|
||||
}
|
||||
},
|
||||
async setup () {
|
||||
|
||||
// Add canvas menu options
|
||||
const orig = LGraphCanvas.prototype.getCanvasMenuOptions;
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
|
||||
const options = orig.apply(this, arguments);
|
||||
|
||||
options.push(null, {
|
||||
content: `Find ♾️Mixlab`,
|
||||
disabled: false, // or a function determining whether to disable
|
||||
callback: async () => {
|
||||
nodesMap =
|
||||
nodesMap && Object.keys(nodesMap).length > 0
|
||||
? nodesMap
|
||||
: await getCustomnodeMappings('url')
|
||||
|
||||
const nodesDiv = document.createDocumentFragment()
|
||||
const nodes = (await app.graphToPrompt()).output
|
||||
|
||||
// console.log('[Mixlab]', 'loaded graph node: ', app)
|
||||
let div =
|
||||
document.querySelector('#mixlab_find_the_node') ||
|
||||
document.createElement('div')
|
||||
div.id = 'mixlab_find_the_node'
|
||||
div.style = `
|
||||
flex-direction: column;
|
||||
align-items: end;
|
||||
display:flex;position: absolute;
|
||||
top: 50px; left: 50px; width: 200px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-menu-bg);
|
||||
padding: 10px;
|
||||
border: 1px solid black;z-index: 999999999;padding-top: 0;`
|
||||
|
||||
div.innerHTML = ''
|
||||
|
||||
let btn = document.createElement('div')
|
||||
btn.style=`display: flex;
|
||||
width: calc(100% - 24px);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
height: 32px;`
|
||||
let btnB = document.createElement('button')
|
||||
let textB = document.createElement('p')
|
||||
btn.appendChild(textB)
|
||||
btn.appendChild(btnB)
|
||||
textB.innerText = `Find The Node`
|
||||
|
||||
btnB.style = `float: right; border: none; color: var(--input-text);
|
||||
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
|
||||
btnB.addEventListener('click', () => {
|
||||
div.style.display = 'none'
|
||||
})
|
||||
btnB.innerText = 'X'
|
||||
|
||||
// 悬浮框拖动事件
|
||||
div.addEventListener('mousedown', function (e) {
|
||||
var startX = e.clientX
|
||||
var startY = e.clientY
|
||||
var offsetX = div.offsetLeft
|
||||
var offsetY = div.offsetTop
|
||||
|
||||
function moveBox (e) {
|
||||
var newX = e.clientX
|
||||
var newY = e.clientY
|
||||
var deltaX = newX - startX
|
||||
var deltaY = newY - startY
|
||||
div.style.left = offsetX + deltaX + 'px'
|
||||
div.style.top = offsetY + deltaY + 'px'
|
||||
}
|
||||
|
||||
function stopMoving () {
|
||||
document.removeEventListener('mousemove', moveBox)
|
||||
document.removeEventListener('mouseup', stopMoving)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', moveBox)
|
||||
document.addEventListener('mouseup', stopMoving)
|
||||
})
|
||||
|
||||
div.appendChild(btn)
|
||||
|
||||
const updateNodes = (ns, nd) => {
|
||||
for (let nodeId in ns) {
|
||||
let n = ns[nodeId].class_type
|
||||
const { url, title } = nodesMap[n]
|
||||
let d = document.createElement('button')
|
||||
d.style = `text-align: left;margin:6px;color: var(--input-text);
|
||||
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
|
||||
d.addEventListener('click', () => {
|
||||
const node = app.graph.getNodeById(nodeId)
|
||||
if (!node) return
|
||||
app.canvas.centerOnNode(node)
|
||||
app.canvas.setZoom(1)
|
||||
})
|
||||
d.addEventListener('mouseover', async () => {
|
||||
// console.log('mouseover')
|
||||
let n = (await app.graphToPrompt()).output
|
||||
if (!deepEqual(n, ns)) {
|
||||
nd.innerHTML = ''
|
||||
updateNodes(n, nd)
|
||||
}
|
||||
})
|
||||
|
||||
d.innerHTML = `
|
||||
<span>${'#' + nodeId} ${n}</span>
|
||||
<a href="${url}" target="_blank" style="text-decoration: none;">🔗</a>
|
||||
`
|
||||
d.title = title
|
||||
|
||||
nd.appendChild(d)
|
||||
}
|
||||
}
|
||||
|
||||
let nodesDivv = document.createElement('div')
|
||||
|
||||
for (let nodeId in nodes) {
|
||||
let n = nodes[nodeId].class_type
|
||||
const { url, title } = nodesMap[n]
|
||||
let d = document.createElement('button')
|
||||
d.style = `text-align: left;margin:6px;color: var(--input-text);
|
||||
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
|
||||
d.addEventListener('click', () => {
|
||||
const node = app.graph.getNodeById(nodeId)
|
||||
if (!node) return
|
||||
app.canvas.centerOnNode(node)
|
||||
app.canvas.setZoom(1)
|
||||
})
|
||||
d.addEventListener('mouseover', async () => {
|
||||
console.log('mouseover')
|
||||
let n = (await app.graphToPrompt()).output
|
||||
if (!deepEqual(n, nodes)) {
|
||||
nodesDivv.innerHTML = ''
|
||||
updateNodes(n, nodesDivv)
|
||||
}
|
||||
})
|
||||
|
||||
d.innerHTML = `
|
||||
<span>${'#' + nodeId} ${n}</span>
|
||||
<a href="${url}" target="_blank" style="text-decoration: none;">🔗</a>
|
||||
`
|
||||
d.title = title
|
||||
|
||||
nodesDiv.appendChild(d)
|
||||
}
|
||||
|
||||
nodesDivv.appendChild(nodesDiv)
|
||||
nodesDivv.style=`overflow: scroll;
|
||||
height: 70vh;width: 100%;`
|
||||
|
||||
div.appendChild(nodesDivv)
|
||||
|
||||
if (!document.querySelector('#mixlab_find_the_node'))
|
||||
document.body.appendChild(div)
|
||||
}
|
||||
});
|
||||
return options;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
@@ -46,6 +47,7 @@ function get_position_style (ctx, widget_width, y, node_height) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.utils.Color',
|
||||
async getCustomWidgets (app) {
|
||||
|
||||
Vendored
+3
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user