mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-01-12 07:10:52 +08:00
Add Output Dir Drawer
Added output directory drawer. On refresh it will load all images in the drawer, on new image trigger it'll add the image to the drawer.
This commit is contained in:
parent
f4b593c44a
commit
9059fc5dcf
20
server.py
20
server.py
@ -116,6 +116,15 @@ class PromptServer():
|
||||
else:
|
||||
return web.Response(status=400)
|
||||
|
||||
@routes.get("/output/images")
|
||||
async def get_output(request):
|
||||
output_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output")
|
||||
|
||||
if not os.path.exists(output_dir):
|
||||
return web.Response(status=404)
|
||||
|
||||
images = [f for f in os.listdir(output_dir) if f.endswith('.png')]
|
||||
return web.json_response({"images": images})
|
||||
|
||||
@routes.get("/view")
|
||||
async def view_image(request):
|
||||
@ -142,13 +151,20 @@ class PromptServer():
|
||||
|
||||
@routes.post("/delete")
|
||||
async def delete(request):
|
||||
body = await request.json()
|
||||
filename = body["delete"]
|
||||
current_dir = os.path.abspath(os.getcwd())
|
||||
output_dir = os.path.join(current_dir, "output")
|
||||
if not os.path.exists(output_dir):
|
||||
return web.json_response({"message": "Output directory does not exist."}, status=404)
|
||||
try:
|
||||
for file_name in os.listdir(output_dir):
|
||||
file_path = os.path.join(output_dir, file_name)
|
||||
if (filename == "all"):
|
||||
for file_name in os.listdir(output_dir):
|
||||
file_path = os.path.join(output_dir, file_name)
|
||||
if os.path.isfile(file_path):
|
||||
os.remove(file_path)
|
||||
else:
|
||||
file_path = os.path.join(output_dir, filename)
|
||||
if os.path.isfile(file_path):
|
||||
os.remove(file_path)
|
||||
return web.json_response({"message": "All content deleted from Output folder."}, status=200)
|
||||
|
||||
@ -7,39 +7,195 @@ import { app } from "/scripts/app.js";
|
||||
app.registerExtension({
|
||||
name: "Comfy.ImageFeed",
|
||||
setup() {
|
||||
const imageList = document.createElement("div");
|
||||
Object.assign(imageList.style, {
|
||||
minHeight: "30px",
|
||||
maxHeight: "300px",
|
||||
width: "100vw",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
background: "#333",
|
||||
overflow: "auto",
|
||||
});
|
||||
document.body.append(imageList);
|
||||
//CODE HERE
|
||||
//create imageList element
|
||||
const imageList = document.createElement("div");
|
||||
Object.assign(imageList.style, {
|
||||
minHeight: "30px",
|
||||
maxHeight: "1000px",
|
||||
width: "100vw",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
background: "#333",
|
||||
overflow: "auto",
|
||||
border: "2px solid #333",
|
||||
zIndex: "99",
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
userSelect: "none",
|
||||
alignContent: "baseline"
|
||||
});
|
||||
|
||||
// add CSS rules for resize cursor
|
||||
const resizeHandle = document.createElement("div");
|
||||
Object.assign(resizeHandle.style, {
|
||||
position: "absolute",
|
||||
top: "-5px",
|
||||
right: "0",
|
||||
left: "0",
|
||||
height: "10px",
|
||||
cursor: "row-resize",
|
||||
zIndex: "1"
|
||||
});
|
||||
imageList.appendChild(resizeHandle);
|
||||
|
||||
// add hover style to resize handle
|
||||
const hoverStyle = document.createElement("style");
|
||||
hoverStyle.innerHTML = `
|
||||
.resize-handle:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(hoverStyle);
|
||||
|
||||
// set class for resize handle
|
||||
resizeHandle.classList.add("resize-handle");
|
||||
|
||||
// add mousedown event listener to resize handle
|
||||
let startY = 0;
|
||||
let startHeight = 0;
|
||||
resizeHandle.addEventListener("mousedown", (event) => {
|
||||
startY = event.clientY;
|
||||
startHeight = parseInt(getComputedStyle(imageList).height);
|
||||
document.addEventListener("mousemove", resize);
|
||||
document.addEventListener("mouseup", stopResize);
|
||||
});
|
||||
|
||||
// resize function
|
||||
function resize(event) {
|
||||
const newHeight = startHeight + startY - event.clientY;
|
||||
imageList.style.height = newHeight + "px";
|
||||
}
|
||||
|
||||
function loadImages(detail) {
|
||||
const allImages = detail.output.images.filter(
|
||||
(img) => img.type === "output" && img.filename !== "_output_images_will_be_put_here"
|
||||
);
|
||||
for (const src of allImages) {
|
||||
const imgContainer = document.createElement("div");
|
||||
imgContainer.style.cssText = "height: 120px; width: 120px; position: relative;";
|
||||
|
||||
const imgDelete = document.createElement("button");
|
||||
imgDelete.innerHTML = "🗑️";
|
||||
imgDelete.style.cssText =
|
||||
"position: absolute; top: 0; right: 0; width: 20px; text-indent: -4px; right: 5px; height: 20px; cursor: pointer; position: absolute; top: 5px; font-size: 12px; line-height: 12px;";
|
||||
|
||||
imgDelete.addEventListener("click", async () => {
|
||||
const confirmDelete = confirm("Are you sure you want to delete this image?");
|
||||
if (confirmDelete) {
|
||||
await api.deleteImage(src.filename);
|
||||
imgContainer.remove();
|
||||
}
|
||||
});
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.style.cssText = "height: 120px; width: 120px; object-fit: cover;";
|
||||
img.src = `/view?filename=${encodeURIComponent(src.filename)}&type=${src.type}&subfolder=${encodeURIComponent(src.subfolder)}`;
|
||||
img.addEventListener("click", () => {
|
||||
const popup = document.createElement("div");
|
||||
popup.style.cssText = "position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 999;";
|
||||
|
||||
const popupImg = document.createElement("img");
|
||||
popupImg.src = img.src;
|
||||
popupImg.style.cssText = "max-height: 80vh; max-width: 80vw;";
|
||||
let currentIndex = allImages.indexOf(src);
|
||||
|
||||
const closeButton = document.createElement("button");
|
||||
closeButton.innerHTML = "❌";
|
||||
closeButton.style.cssText = "position: absolute; top: 0; right: 0; padding: 5px; font-size: 20px; line-height: 20px; background-color: transparent; border: none; color: white; cursor: pointer;";
|
||||
|
||||
const nextButton = document.createElement("button");
|
||||
nextButton.innerHTML = "▶";
|
||||
nextButton.style.cssText = "position: absolute; top: 50%; right: 10px; padding: 5px; font-size: 20px; line-height: 20px; background-color: transparent; border: none; color: white; cursor: pointer; transform: translateY(-50%);";
|
||||
|
||||
const prevButton = document.createElement("button");
|
||||
prevButton.innerHTML = "◀";
|
||||
prevButton.style.cssText = "position: absolute; top: 50%; left: 10px; padding: 5px; font-size: 20px; line-height: 20px; background-color: transparent; border: none; color: white; cursor: pointer; transform: translateY(-50%);";
|
||||
|
||||
closeButton.addEventListener("click", () => {
|
||||
popup.remove();
|
||||
});
|
||||
nextButton.addEventListener("click", () => {
|
||||
currentIndex--;
|
||||
if (currentIndex < 0) {
|
||||
currentIndex = allImages.length - 1;
|
||||
}
|
||||
popupImg.src = `/view?filename=${encodeURIComponent(allImages[currentIndex].filename)}&type=${allImages[currentIndex].type}&subfolder=${encodeURIComponent(allImages[currentIndex].subfolder)}`;
|
||||
});
|
||||
prevButton.addEventListener("click", () => {
|
||||
currentIndex++;
|
||||
if (currentIndex >= allImages.length) {
|
||||
currentIndex = 0;
|
||||
}
|
||||
popupImg.src = `/view?filename=${encodeURIComponent(allImages[currentIndex].filename)}&type=${allImages[currentIndex].type}&subfolder=${encodeURIComponent(allImages[currentIndex].subfolder)}`;
|
||||
});
|
||||
popup.addEventListener("click", (event) => {
|
||||
if (event.target === popup) {
|
||||
popup.remove();
|
||||
}
|
||||
});
|
||||
popup.append(popupImg);
|
||||
popup.append(closeButton);
|
||||
popup.append(nextButton);
|
||||
popup.append(prevButton);
|
||||
document.body.append(popup);
|
||||
});
|
||||
|
||||
|
||||
imgContainer.append(imgDelete);
|
||||
imgContainer.append(img);
|
||||
imageList.prepend(imgContainer);
|
||||
}
|
||||
}
|
||||
|
||||
api.getOutput().then(data => {
|
||||
try {
|
||||
var images = data.filenames[0].map((filename) => {
|
||||
return { filename: filename, type: 'output', subfolder: '' };
|
||||
});
|
||||
var output = {images: images}
|
||||
var detail = {output: output}
|
||||
loadImages(detail);
|
||||
} catch(err){}
|
||||
});
|
||||
|
||||
// stop resize function
|
||||
function stopResize() {
|
||||
document.removeEventListener("mousemove", resize);
|
||||
document.removeEventListener("mouseup", stopResize);
|
||||
}
|
||||
|
||||
// append imageList element to document
|
||||
document.body.append(imageList);
|
||||
const menu = document.createElement("div");
|
||||
Object.assign(menu.style, {
|
||||
height: "100%",
|
||||
width: "90px",
|
||||
right:"0px",
|
||||
top:"0px"
|
||||
});
|
||||
imageList.append(menu);
|
||||
function makeButton(text, style) {
|
||||
const btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.textContent = text;
|
||||
Object.assign(btn.style, {
|
||||
...style,
|
||||
height: "20px",
|
||||
cursor: "pointer",
|
||||
position: "absolute",
|
||||
top: "5px",
|
||||
fontSize: "12px",
|
||||
lineHeight: "12px",
|
||||
});
|
||||
imageList.append(btn);
|
||||
Object.assign(btn.style, {
|
||||
...style,
|
||||
height: "20px",
|
||||
width: "80px",
|
||||
cursor: "pointer",
|
||||
position: "absolute",
|
||||
fontSize: "12px",
|
||||
lineHeight: "12px",
|
||||
});
|
||||
menu.append(btn);
|
||||
return btn;
|
||||
}
|
||||
|
||||
const showButton = document.createElement("button");
|
||||
const closeButton = makeButton("❌", {
|
||||
width: "20px",
|
||||
const closeButton = makeButton("❌ Close", {
|
||||
textIndent: "-4px",
|
||||
top: "5px",
|
||||
right: "5px",
|
||||
});
|
||||
closeButton.onclick = () => {
|
||||
@ -47,12 +203,21 @@ app.registerExtension({
|
||||
showButton.style.display = "unset";
|
||||
};
|
||||
|
||||
const clearButton = makeButton("Clear", {
|
||||
right: "30px",
|
||||
const clearButton = makeButton("✖ Clear", {
|
||||
top: "30px",
|
||||
right: "5px",
|
||||
});
|
||||
clearButton.onclick = () => {
|
||||
imageList.replaceChildren(closeButton, clearButton);
|
||||
};
|
||||
const deleteAllButton = makeButton("🗑️ Delete", {
|
||||
top: "55px",
|
||||
right: "5px",
|
||||
});
|
||||
deleteAllButton.onclick = () => {
|
||||
api.deleteAllImages();
|
||||
imageList.replaceChildren(closeButton, clearButton);
|
||||
};
|
||||
|
||||
showButton.classList.add("comfy-settings-btn");
|
||||
showButton.style.right = "16px";
|
||||
@ -60,33 +225,13 @@ app.registerExtension({
|
||||
showButton.style.display = "none";
|
||||
showButton.textContent = "🖼️";
|
||||
showButton.onclick = () => {
|
||||
imageList.style.display = "block";
|
||||
imageList.style.display = "flex";
|
||||
showButton.style.display = "none";
|
||||
};
|
||||
document.querySelector(".comfy-settings-btn").after(showButton);
|
||||
|
||||
api.addEventListener("executed", ({ detail }) => {
|
||||
if (detail?.output?.images) {
|
||||
for (const src of detail.output.images) {
|
||||
if (src.type == 'output') { //Only show output images, not preview images
|
||||
const img = document.createElement("img");
|
||||
const a = document.createElement("a");
|
||||
a.href = `/view?filename=${encodeURIComponent(src.filename)}&type=${src.type}&subfolder=${encodeURIComponent(
|
||||
src.subfolder
|
||||
)}`;
|
||||
a.target = "_blank";
|
||||
Object.assign(img.style, {
|
||||
height: "120px",
|
||||
width: "120px",
|
||||
objectFit: "cover",
|
||||
});
|
||||
|
||||
img.src = a.href;
|
||||
a.append(img);
|
||||
imageList.prepend(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
loadImages(detail);
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -123,6 +123,9 @@ class ComfyApi extends EventTarget {
|
||||
await this.#postItem("delete", { delete: "all" })
|
||||
}
|
||||
}
|
||||
async deleteImage(filename) {
|
||||
await this.#postItem("delete", { delete: filename })
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of embedding names
|
||||
@ -223,6 +226,20 @@ class ComfyApi extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the prompt execution history
|
||||
* @returns Prompt history including node outputs
|
||||
*/
|
||||
async getOutput() {
|
||||
try {
|
||||
const res = await fetch("/output/images");
|
||||
return { filenames: Object.values(await res.json()) };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { output: [] };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a POST request to the API
|
||||
* @param {*} type The endpoint to post to
|
||||
|
||||
Loading…
Reference in New Issue
Block a user