working demo
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
import {ComfyDialog} from "../../scripts/ui.js";
|
||||
import {startVideo} from "./youtube.js";
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
|
||||
const docStyle = document.createElement('style');
|
||||
docStyle.innerHTML = `
|
||||
.learn-menu {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 60vw;
|
||||
color: white;
|
||||
|
||||
}
|
||||
|
||||
.category-list {
|
||||
flex: 0 0 10%;
|
||||
border-right: 1px solid #ccc;
|
||||
padding-right: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.category-item{
|
||||
border: 1px solid #ccc;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.module-list {
|
||||
flex: 1;
|
||||
padding-left: 1em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.module-item {
|
||||
border: 1px solid #ccc;
|
||||
padding: 1em;
|
||||
margin-bottom: 1em;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.module-item img {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
.module-item-details{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.module-item button {
|
||||
background: #007BFF;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
text-align: right;
|
||||
text-decoration: none;
|
||||
justify-content: right;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin: 10px 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
document.head.appendChild(docStyle);
|
||||
|
||||
export const learnMenu = document.createElement('div');
|
||||
learnMenu.classList.add("learn-menu")
|
||||
|
||||
|
||||
const tutorials = [
|
||||
{
|
||||
title:"How to get started with ComfyUI",
|
||||
description:`How and why<b> to get started with ComfyUI</b>. Join to OpenArt Contest with a Price Pool of over $13000 USD https://contest.openart.ai/#participate
|
||||
This ComfyUi Starter Guide shows you how to install everything and get started. Learn ComfyUI with easy Workflow examples. Build and Sell powerful Workflows in no time.`,
|
||||
videoId:`5dBAIhx28ow`,
|
||||
categories:["All"],
|
||||
initialWorkflow:`{
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 9,
|
||||
"nodes": [],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}`
|
||||
}, {
|
||||
title:"How to use IP-Adapter",
|
||||
description:`In this tutorial we will learn the how to use one of the most powerful tools in stable diffusion - the IP-Adapter`,
|
||||
videoId:`yrqT5FaBoIU`,
|
||||
categories:["All"],
|
||||
initialWorkflow:`{
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 9,
|
||||
"nodes": [],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}`,
|
||||
keyframesWorkflows: {
|
||||
5: `{"last_node_id": 9,"last_link_id": 9,"nodes": [{"id": 7,"type": "CLIPTextEncode","pos": [413,389],"size": {"0": 425.27801513671875,"1": 180.6060791015625},"flags": {},"order": 3,"mode": 0,"inputs": [{"name": "clip","type": "CLIP","link": 5}],"outputs": [{"name": "CONDITIONING","type": "CONDITIONING","links": [],"slot_index": 0}],"properties": {"Node name for S&R": "CLIPTextEncode"},"widgets_values": ["text, watermark"]},{"id": 6,"type": "CLIPTextEncode","pos": [415,186],"size": {"0": 422.84503173828125,"1": 164.31304931640625},"flags": {},"order": 2,"mode": 0,"inputs": [{"name": "clip","type": "CLIP","link": 3}],"outputs": [{"name": "CONDITIONING","type": "CONDITIONING","links": [],"slot_index": 0}],"properties": {"Node name for S&R": "CLIPTextEncode"},"widgets_values": ["beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"]},{"id": 5,"type": "EmptyLatentImage","pos": [473,609],"size": {"0": 315,"1": 106},"flags": {},"order": 0,"mode": 0,"outputs": [{"name": "LATENT","type": "LATENT","links": [],"slot_index": 0}],"properties": {"Node name for S&R": "EmptyLatentImage"},"widgets_values": [512,512,1]},{"id": 4,"type": "CheckpointLoaderSimple","pos": [26,474],"size": {"0": 315,"1": 98},"flags": {},"order": 1,"mode": 0,"outputs": [{"name": "MODEL","type": "MODEL","links": [],"slot_index": 0},{"name": "CLIP","type": "CLIP","links": [3,5],"slot_index": 1},{"name": "VAE","type": "VAE","links": [],"slot_index": 2}],"properties": {"Node name for S&R": "CheckpointLoaderSimple"},"widgets_values": ["v1-5-pruned-emaonly.ckpt"]}],"links": [[3,4,1,6,0,"CLIP"],[5,4,1,7,0,"CLIP"]],"groups": [],"config": {},"extra": {},"version": 0.4}`,
|
||||
10:`{"last_node_id": 9,"last_link_id": 9,"nodes": [{"id": 7,"type": "CLIPTextEncode","pos": [413,389],"size": {"0": 425.27801513671875,"1": 180.6060791015625},"flags": {},"order": 3,"mode": 0,"inputs": [{"name": "clip","type": "CLIP","link": 5}],"outputs": [{"name": "CONDITIONING","type": "CONDITIONING","links": [6],"slot_index": 0}],"properties": {"Node name for S&R": "CLIPTextEncode"},"widgets_values": ["text, watermark"]},{"id": 6,"type": "CLIPTextEncode","pos": [415,186],"size": {"0": 422.84503173828125,"1": 164.31304931640625},"flags": {},"order": 2,"mode": 0,"inputs": [{"name": "clip","type": "CLIP","link": 3}],"outputs": [{"name": "CONDITIONING","type": "CONDITIONING","links": [4],"slot_index": 0}],"properties": {"Node name for S&R": "CLIPTextEncode"},"widgets_values": ["beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"]},{"id": 5,"type": "EmptyLatentImage","pos": [473,609],"size": {"0": 315,"1": 106},"flags": {},"order": 0,"mode": 0,"outputs": [{"name": "LATENT","type": "LATENT","links": [2],"slot_index": 0}],"properties": {"Node name for S&R": "EmptyLatentImage"},"widgets_values": [512,512,1]},{"id": 3,"type": "KSampler","pos": [863,186],"size": {"0": 315,"1": 262},"flags": {},"order": 4,"mode": 0,"inputs": [{"name": "model","type": "MODEL","link": 1},{"name": "positive","type": "CONDITIONING","link": 4},{"name": "negative","type": "CONDITIONING","link": 6},{"name": "latent_image","type": "LATENT","link": 2}],"outputs": [{"name": "LATENT","type": "LATENT","links": [7],"slot_index": 0}],"properties": {"Node name for S&R": "KSampler"},"widgets_values": [156680208700286,"randomize",20,8,"euler","normal",1]},{"id": 8,"type": "VAEDecode","pos": [1209,188],"size": {"0": 210,"1": 46},"flags": {},"order": 5,"mode": 0,"inputs": [{"name": "samples","type": "LATENT","link": 7},{"name": "vae","type": "VAE","link": 8}],"outputs": [{"name": "IMAGE","type": "IMAGE","links": [9],"slot_index": 0}],"properties": {"Node name for S&R": "VAEDecode"}},{"id": 9,"type": "SaveImage","pos": [1451,189],"size": {"0": 210,"1": 58},"flags": {},"order": 6,"mode": 0,"inputs": [{"name": "images","type": "IMAGE","link": 9}],"properties": {},"widgets_values": ["ComfyUI"]},{"id": 4,"type": "CheckpointLoaderSimple","pos": [26,474],"size": {"0": 315,"1": 98},"flags": {},"order": 1,"mode": 0,"outputs": [{"name": "MODEL","type": "MODEL","links": [1],"slot_index": 0},{"name": "CLIP","type": "CLIP","links": [3,5],"slot_index": 1},{"name": "VAE","type": "VAE","links": [8],"slot_index": 2}],"properties": {"Node name for S&R": "CheckpointLoaderSimple"},"widgets_values": ["v1-5-pruned-emaonly.ckpt"]}],"links": [[1,4,0,3,0,"MODEL"],[2,5,0,3,3,"LATENT"],[3,4,1,6,0,"CLIP"],[4,6,0,3,1,"CONDITIONING"],[5,4,1,7,0,"CLIP"],[6,7,0,3,2,"CONDITIONING"],[7,3,0,8,0,"LATENT"],[8,4,2,8,1,"VAE"],[9,8,0,9,0,"IMAGE"]],"groups": [],"config": {},"extra": {},"version": 0.4}`
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
const categories = ["All"]
|
||||
|
||||
|
||||
const categoriesEl = document.createElement('div')
|
||||
categoriesEl.classList.add('category-list')
|
||||
categoriesEl.innerHTML = `<h3>Categories</h3>`
|
||||
learnMenu.appendChild(categoriesEl)
|
||||
const moduleListEl = document.createElement('div')
|
||||
moduleListEl.classList.add('module-list')
|
||||
learnMenu.appendChild(moduleListEl)
|
||||
|
||||
for (let category of categories) {
|
||||
const cat = document.createElement('li');
|
||||
cat.classList.add('category-item');
|
||||
cat.textContent = category;
|
||||
cat.onclick = ()=>loadCategory(category)
|
||||
categoriesEl.appendChild(cat);
|
||||
}
|
||||
|
||||
function loadCategory(category){
|
||||
moduleListEl.innerHTML = "";
|
||||
for (let tutorial of tutorials) {
|
||||
if (tutorial.categories.includes(category) || category==="All") {
|
||||
const moduleItem = document.createElement('div');
|
||||
moduleItem.classList.add('module-item');
|
||||
moduleItem.innerHTML = `
|
||||
<img src="https://img.youtube.com/vi/${tutorial.videoId}/default.jpg" alt="module image">
|
||||
<div class="module-item-details">
|
||||
<h2>${tutorial.title}</h2>
|
||||
<p>${tutorial.description}</p>
|
||||
</div>
|
||||
<button>Learn!</button>
|
||||
`;
|
||||
moduleListEl.appendChild(moduleItem);
|
||||
moduleItem.querySelector('button').addEventListener('click', () => {
|
||||
LearnModal.get().close()
|
||||
if(tutorial.initialWorkflow){
|
||||
app.loadGraphData(JSON.parse(tutorial.initialWorkflow))
|
||||
}
|
||||
const keyframesFunctions = {}
|
||||
if(tutorial.keyframesWorkflows){
|
||||
for (let keyframe in tutorial.keyframesWorkflows) {
|
||||
const parsedWorkflow = JSON.parse(tutorial.keyframesWorkflows[keyframe])
|
||||
keyframesFunctions[keyframe] = ()=> app.loadGraphData(parsedWorkflow);
|
||||
}
|
||||
}
|
||||
startVideo(tutorial.title,tutorial.videoId,keyframesFunctions)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadCategory("All")
|
||||
|
||||
export class LearnModal extends ComfyDialog{
|
||||
static instance=null
|
||||
show(){
|
||||
super.show(learnMenu);
|
||||
}
|
||||
|
||||
static get(){
|
||||
if (LearnModal.instance === null) {
|
||||
LearnModal.instance = new LearnModal();
|
||||
}
|
||||
return LearnModal.instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import {LearnModal} from './learn-menu.js'
|
||||
|
||||
|
||||
var docStyle = document.createElement('style');
|
||||
docStyle.innerHTML = `
|
||||
@keyframes Gradient {
|
||||
0%{background-position: 0% 50%}
|
||||
50%{background-position: 100% 50%}
|
||||
100%{background-position: 0% 50%}
|
||||
}
|
||||
`;
|
||||
|
||||
document.head.appendChild(docStyle);
|
||||
|
||||
let learnModalInstance = null;
|
||||
|
||||
|
||||
|
||||
const ext = {
|
||||
// Unique name for the extension
|
||||
name: "ComfyUI-Assistant",
|
||||
async setup(app) {
|
||||
const menuLoadDefault = document.querySelector("#comfy-load-default-button");
|
||||
const learnBtn = document.createElement("button");
|
||||
learnBtn.textContent = "Learn";
|
||||
|
||||
if(!localStorage.getItem("clickedLearn")) {
|
||||
learnBtn.style.background = "linear-gradient(270deg, #d53369, #daae51)";
|
||||
learnBtn.style.backgroundSize = "200% 200%";
|
||||
learnBtn.style.animation = "Gradient 2s ease infinite";
|
||||
learnBtn.addEventListener("click", ()=>{
|
||||
localStorage.setItem("clickedLearn","true")
|
||||
learnBtn.style=null;
|
||||
})
|
||||
}
|
||||
learnBtn.addEventListener("click", ()=>{
|
||||
LearnModal.get().show();
|
||||
})
|
||||
menuLoadDefault.after(learnBtn);
|
||||
},
|
||||
};
|
||||
|
||||
app.registerExtension(ext);
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
|
||||
|
||||
const tag = document.createElement('script');
|
||||
|
||||
tag.src = "https://www.youtube.com/iframe_api";
|
||||
const firstScriptTag = document.getElementsByTagName('script')[0];
|
||||
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
||||
|
||||
let player;
|
||||
const setVideo = (videoId)=> {
|
||||
console.log(videoId)
|
||||
player = new YT.Player('player',{
|
||||
videoId: videoId,
|
||||
events: {
|
||||
'onStateChange': onPlayerStateChange
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var done = false;
|
||||
var lastInterval = null;
|
||||
let keyframesFunctions= {}
|
||||
function onPlayerStateChange(event) {
|
||||
if (event.data == YT.PlayerState.PLAYING) {
|
||||
lastInterval = setInterval(function () {
|
||||
// keyframesFunctions contains seconds and functions like {5:()=>console.log("five seconds!")} I want that this will always call the appropriate function based on the time but still taking to considerations interval hiccups
|
||||
const currentTime = player.getCurrentTime();
|
||||
const timeInSeconds = Math.floor(currentTime);
|
||||
if (keyframesFunctions[timeInSeconds]) {
|
||||
keyframesFunctions[timeInSeconds]();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function startVideo(title,videoId, _keyframesFunctions){
|
||||
keyframesFunctions = _keyframesFunctions
|
||||
const lastContainer = document.getElementById("youtube-container")
|
||||
if(lastContainer){
|
||||
lastContainer.remove()
|
||||
clearInterval(lastInterval)
|
||||
}
|
||||
const ytContainer = document.createElement('div');
|
||||
ytContainer.style.position="absolute"
|
||||
ytContainer.style.top="50px"
|
||||
ytContainer.style.left="50px"
|
||||
ytContainer.style.zIndex="101"
|
||||
ytContainer.setAttribute("id","youtube-container")
|
||||
|
||||
|
||||
const videoContainerWindow = document.createElement('div');
|
||||
videoContainerWindow.style.width="100%"
|
||||
videoContainerWindow.style.height="30px"
|
||||
videoContainerWindow.style.background="#5e5e5e"
|
||||
videoContainerWindow.style.cursor="move";
|
||||
videoContainerWindow.style.display= "flex";
|
||||
videoContainerWindow.style.alignItems= "center";
|
||||
const titleEl = document.createElement('span')
|
||||
titleEl.style.flexGrow= "1";
|
||||
titleEl.style.padding= "4px";
|
||||
titleEl.appendChild(document.createTextNode(title))
|
||||
|
||||
videoContainerWindow.appendChild(titleEl);
|
||||
//Add minimize and exit:
|
||||
const minimizeButton = document.createElement('button');
|
||||
minimizeButton.style.marginRight = "5px";
|
||||
minimizeButton.innerHTML = "-";
|
||||
|
||||
minimizeButton.onclick = () => {
|
||||
document.getElementById("player").hidden=!document.getElementById("player").hidden
|
||||
}
|
||||
|
||||
const exitButton = document.createElement('button');
|
||||
exitButton.innerHTML = "X";
|
||||
exitButton.style.marginRight="5px";
|
||||
|
||||
exitButton.onclick = () => {
|
||||
ytContainer.remove();
|
||||
}
|
||||
|
||||
videoContainerWindow.appendChild(minimizeButton);
|
||||
videoContainerWindow.appendChild(exitButton);
|
||||
|
||||
|
||||
|
||||
let drag = {x: 0, y: 0};
|
||||
let dragging = false;
|
||||
videoContainerWindow.onmousedown = (e) => {
|
||||
drag.x = e.clientX - ytContainer.offsetLeft;
|
||||
drag.y = e.clientY - ytContainer.offsetTop;
|
||||
dragging=true
|
||||
}
|
||||
|
||||
document.onmousemove = (e) => {
|
||||
if (e.buttons === 1 && dragging) {
|
||||
ytContainer.style.left = `${e.clientX - drag.x}px`;
|
||||
ytContainer.style.top = `${e.clientY - drag.y}px`;
|
||||
}
|
||||
}
|
||||
|
||||
document.onmouseup = (e) => {
|
||||
drag = {x: 0, y: 0};
|
||||
dragging=false;
|
||||
}
|
||||
|
||||
|
||||
ytContainer.appendChild(videoContainerWindow)
|
||||
const ytPlayer = document.createElement('div');
|
||||
ytPlayer.setAttribute("id","player")
|
||||
ytContainer.appendChild(ytPlayer)
|
||||
document.body.appendChild(ytContainer);
|
||||
setVideo(videoId)
|
||||
}
|
||||
Reference in New Issue
Block a user