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:
Silversith 2023-04-07 14:26:38 +02:00
parent f4b593c44a
commit 9059fc5dcf
3 changed files with 227 additions and 49 deletions

View File

@ -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)

View File

@ -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);
});
},
});

View File

@ -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