ComfyUI-Manager/js/comfyui-share-copus.js
copusDev bd752550a8
feat: change web icon (#2042)
Co-authored-by: john <john@server31.io>
2025-07-30 18:31:56 +09:00

1169 lines
32 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { app } from "../../scripts/app.js";
import { $el, ComfyDialog } from "../../scripts/ui.js";
import { customAlert } from "./common.js";
const env = "prod";
let DEFAULT_HOMEPAGE_URL = "https://copus.io";
let API_ENDPOINT = "https://api.client.prod.copus.io";
if (env !== "prod") {
API_ENDPOINT = "https://api.test.copus.io";
DEFAULT_HOMEPAGE_URL = "https://test.copus.io";
}
const style = `
.copus-share-dialog a {
color: #f8f8f8;
}
.copus-share-dialog a:hover {
color: #007bff;
}
.output_label {
border: 5px solid transparent;
}
.output_label:hover {
border: 5px solid #59E8C6;
}
.output_label.checked {
border: 5px solid #59E8C6;
}
`;
// Shared component styles
const sectionStyle = {
marginBottom: 0,
padding: 0,
borderRadius: "8px",
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)",
display: "flex",
flexDirection: "column",
justifyContent: "center",
position: "relative",
};
export class CopusShareDialog extends ComfyDialog {
static instance = null;
constructor() {
super();
$el("style", {
textContent: style,
parent: document.head,
});
this.element = $el(
"div.comfy-modal.copus-share-dialog",
{
parent: document.body,
style: {
"overflow-y": "auto",
},
},
[$el("div.comfy-modal-content", {}, [...this.createButtons()])]
);
this.selectedOutputIndex = 0;
this.selectedOutput_lock = 0;
this.selectedNodeId = null;
this.uploadedImages = [];
this.allFilesImages = [];
this.selectedFile = null;
this.allFiles = [];
this.titleNum = 0;
}
createButtons() {
const inputStyle = {
display: "block",
minWidth: "500px",
width: "100%",
padding: "10px",
margin: "10px 0",
borderRadius: "4px",
border: "1px solid #ddd",
boxSizing: "border-box",
};
const textAreaStyle = {
display: "block",
minWidth: "500px",
width: "100%",
padding: "10px",
margin: "10px 0",
borderRadius: "4px",
border: "1px solid #ddd",
boxSizing: "border-box",
minHeight: "100px",
background: "#222",
resize: "vertical",
color: "#f2f2f2",
fontFamily: "Arial",
fontWeight: "400",
fontSize: "15px",
};
const hyperLinkStyle = {
display: "block",
marginBottom: "15px",
fontWeight: "bold",
fontSize: "14px",
};
const labelStyle = {
color: "#f8f8f8",
display: "block",
margin: "10px 0 0 0",
fontWeight: "bold",
textDecoration: "none",
};
const buttonStyle = {
padding: "10px 80px",
margin: "10px 5px",
borderRadius: "4px",
border: "none",
cursor: "pointer",
color: "#fff",
backgroundColor: "#007bff",
};
// upload images input
this.uploadImagesInput = $el("input", {
type: "file",
multiple: false,
style: inputStyle,
accept: "image/*",
});
this.uploadImagesInput.addEventListener("change", async (e) => {
const file = e.target.files[0];
if (!file) {
this.previewImage.src = "";
this.previewImage.style.display = "none";
return;
}
const reader = new FileReader();
reader.onload = async (e) => {
const imgData = e.target.result;
this.previewImage.src = imgData;
this.previewImage.style.display = "block";
this.selectedFile = null;
// Once user uploads an image, we uncheck all radio buttons
this.radioButtons.forEach((ele) => {
ele.checked = false;
ele.parentElement.classList.remove("checked");
});
// Add the opacity style toggle here to indicate that they only need
// to upload one image or choose one from the outputs.
this.outputsSection.style.opacity = 0.35;
this.uploadImagesInput.style.opacity = 1;
};
reader.readAsDataURL(file);
});
// preview image
this.previewImage = $el("img", {
src: "",
style: {
width: "100%",
maxHeight: "100px",
objectFit: "contain",
display: "none",
marginTop: "10px",
},
});
this.keyInput = $el("input", {
type: "password",
placeholder: "Copy & paste your API key",
style: inputStyle,
});
this.TitleInput = $el("input", {
type: "text",
placeholder: "Title (Required)",
style: inputStyle,
maxLength: "70",
oninput: () => {
const titleNum = this.TitleInput.value.length;
titleNumDom.textContent = `${titleNum}/70`;
},
});
this.SubTitleInput = $el("input", {
type: "text",
placeholder: "Subtitle (Optional)",
style: inputStyle,
maxLength: "350",
oninput: () => {
const titleNum = this.SubTitleInput.value.length;
subTitleNumDom.textContent = `${titleNum}/350`;
},
});
this.LockInput = $el("input", {
type: "text",
placeholder: "0",
style: {
width: "100px",
padding: "7px",
paddingLeft: "30px",
borderRadius: "4px",
border: "1px solid #ddd",
boxSizing: "border-box",
position: "relative",
},
oninput: (event) => {
let input = event.target.value;
// Use a regular expression to match a number with up to two decimal places
const regex = /^\d*\.?\d{0,2}$/;
if (!regex.test(input)) {
// If the input doesn't match, remove the last entered character
event.target.value = input.slice(0, -1);
}
const numericValue = parseFloat(input);
if (numericValue > 9999) {
input = "9999";
}
// Update the input field with the valid value
event.target.value = input;
},
});
this.descriptionInput = $el("textarea", {
placeholder: "Content (Optional)",
style: {
...textAreaStyle,
minHeight: "100px",
},
});
// Header Section
const headerSection = $el("h3", {
textContent: "Share your workflow to Copus",
size: 3,
color: "white",
style: {
"text-align": "center",
color: "white",
margin: "0 0 10px 0",
},
});
this.getAPIKeyLink = $el(
"a",
{
style: {
...hyperLinkStyle,
color: "#59E8C6",
},
href: `${DEFAULT_HOMEPAGE_URL}?fromPage=comfyUI`,
target: "_blank",
},
["👉 Get your API key here"]
);
const linkSection = $el(
"div",
{
style: {
marginTop: "10px",
display: "flex",
flexDirection: "column",
},
},
[
// this.communityLink,
this.getAPIKeyLink,
]
);
// Account Section
const accountSection = $el("div", { style: sectionStyle }, [
$el("label", { style: labelStyle }, ["1⃣ Copus API Key"]),
this.keyInput,
]);
// Output Upload Section
const outputUploadSection = $el("div", { style: sectionStyle }, [
$el(
"label",
{
style: {
...labelStyle,
margin: "10px 0 0 0",
},
},
["2⃣ Image/Thumbnail (Required)"]
),
this.previewImage,
this.uploadImagesInput,
]);
// Outputs Section
this.outputsSection = $el(
"div",
{
id: "selectOutputs",
},
[]
);
const titleNumDom = $el(
"label",
{
style: {
fontSize: "12px",
position: "absolute",
right: "10px",
bottom: "-10px",
color: "#999",
},
},
["0/70"]
);
const subTitleNumDom = $el(
"label",
{
style: {
fontSize: "12px",
position: "absolute",
right: "10px",
bottom: "-10px",
color: "#999",
},
},
["0/350"]
);
const descriptionNumDom = $el(
"label",
{
style: {
fontSize: "12px",
position: "absolute",
right: "10px",
bottom: "-10px",
color: "#999",
},
},
["0/70"]
);
// Additional Inputs Section
const additionalInputsSection = $el("div", { style: { ...sectionStyle } }, [
$el("label", { style: labelStyle }, ["3⃣ Title "]),
this.TitleInput,
titleNumDom,
]);
const SubtitleSection = $el("div", { style: sectionStyle }, [
$el("label", { style: labelStyle }, ["4⃣ Subtitle "]),
this.SubTitleInput,
subTitleNumDom,
]);
const DescriptionSection = $el("div", { style: sectionStyle }, [
$el("label", { style: labelStyle }, ["5⃣ Description "]),
this.descriptionInput,
// descriptionNumDom,
]);
// switch between outputs section and additional inputs section
this.radioButtons_lock = [];
this.radioButtonsCheck_lock = $el("input", {
type: "radio",
name: "output_type_lock",
value: "0",
id: "blockchain1_lock",
checked: true,
});
this.radioButtonsCheckOff_lock = $el("input", {
type: "radio",
name: "output_type_lock",
value: "1",
id: "blockchain_lock",
});
const blockChainSection_lock = $el("div", { style: sectionStyle }, [
$el("label", { style: labelStyle }, ["6⃣ Download threshold"]),
$el(
"label",
{
style: {
marginTop: "10px",
display: "flex",
alignItems: "center",
cursor: "pointer",
},
},
[
this.radioButtonsCheck_lock,
$el(
"div",
{
style: {
marginLeft: "5px",
display: "flex",
alignItems: "center",
position: "relative",
},
},
[
$el("span", { style: { marginLeft: "5px" } }, ["ON"]),
$el(
"span",
{
style: {
marginLeft: "20px",
marginRight: "10px",
color: "#fff",
},
},
["Unlock with"]
),
$el("img", {
style: {
width: "16px",
height: "16px",
position: "absolute",
right: "75px",
zIndex: "100",
},
src: "https://static.copus.io/images/admin/202507/prod/e2919a1d8f3c2d99d3b8fe27ff94b841.png",
}),
this.LockInput,
]
),
]
),
$el(
"label",
{ style: { display: "flex", alignItems: "center", cursor: "pointer" } },
[
this.radioButtonsCheckOff_lock,
$el(
"div",
{
style: {
marginLeft: "5px",
display: "flex",
alignItems: "center",
},
},
[$el("span", { style: { marginLeft: "5px" } }, ["OFF"])]
),
]
),
$el(
"p",
{ style: { fontSize: "16px", color: "#fff", margin: "10px 0 0 0" } },
[
]
),
]);
this.radioButtons = [];
this.radioButtonsCheck = $el("input", {
type: "radio",
name: "output_type",
value: "0",
id: "blockchain1",
checked: true,
});
this.radioButtonsCheckOff = $el("input", {
type: "radio",
name: "output_type",
value: "1",
id: "blockchain",
});
const blockChainSection = $el("div", { style: sectionStyle }, [
$el("label", { style: labelStyle }, ["8⃣ Store on blockchain "]),
$el(
"label",
{
style: {
marginTop: "10px",
display: "flex",
alignItems: "center",
cursor: "pointer",
},
},
[
this.radioButtonsCheck,
$el("span", { style: { marginLeft: "5px" } }, ["ON"]),
]
),
$el(
"label",
{ style: { display: "flex", alignItems: "center", cursor: "pointer" } },
[
this.radioButtonsCheckOff,
$el("span", { style: { marginLeft: "5px" } }, ["OFF"]),
]
),
$el(
"p",
{ style: { fontSize: "16px", color: "#fff", margin: "10px 0 0 0" } },
["Secure ownership with a permanent & decentralized storage"]
),
]);
this.ratingRadioButtonsCheck0 = $el("input", {
type: "radio",
name: "content_rating",
value: "0",
id: "content_rating0",
});
this.ratingRadioButtonsCheck1 = $el("input", {
type: "radio",
name: "content_rating",
value: "1",
id: "content_rating1",
});
this.ratingRadioButtonsCheck2 = $el("input", {
type: "radio",
name: "content_rating",
value: "2",
id: "content_rating2",
});
this.ratingRadioButtonsCheck_1 = $el("input", {
type: "radio",
name: "content_rating",
value: "-1",
id: "content_rating_1",
checked: true,
});
// content rating
const contentRatingSection = $el("div", { style: sectionStyle }, [
$el("label", { style: labelStyle }, ["7⃣ Content rating "]),
$el(
"label",
{
style: {
marginTop: "10px",
display: "flex",
alignItems: "center",
cursor: "pointer",
},
},
[
this.ratingRadioButtonsCheck0,
$el("img", {
style: {
width: "12px",
height: "12px",
marginLeft: "5px",
},
src: "https://static.copus.io/images/client/202507/test/b9f17da83b054d53cd0cb4508c2c30dc.png",
}),
$el("span", { style: { marginLeft: "5px", color: "#fff" } }, [
"All ages",
]),
]
),
$el(
"p",
{ style: { fontSize: "10px", color: "#fff", marginLeft: "20px" } },
["Safe for all viewers; no profanity, violence, or mature themes."]
),
$el(
"label",
{ style: { display: "flex", alignItems: "center", cursor: "pointer" } },
[
this.ratingRadioButtonsCheck1,
$el("img", {
style: {
width: "12px",
height: "12px",
marginLeft: "5px",
},
src: "https://static.copus.io/images/client/202507/test/7848bc0d3690671df21c7cf00c4cfc81.png",
}),
$el("span", { style: { marginLeft: "5px", color: "#fff" } }, [
"13+ (Teen)",
]),
]
),
$el(
"p",
{ style: { fontSize: "10px", color: "#fff", marginLeft: "20px" } },
[
"Mild language, light themes, or cartoon violence; no explicit content. ",
]
),
$el(
"label",
{ style: { display: "flex", alignItems: "center", cursor: "pointer" } },
[
this.ratingRadioButtonsCheck2,
$el("img", {
style: {
width: "12px",
height: "12px",
marginLeft: "5px",
},
src: "https://static.copus.io/images/client/202507/test/bc51839c208d68d91173e43c23bff039.png",
}),
$el("span", { style: { marginLeft: "5px", color: "#fff" } }, [
"18+ (Explicit)",
]),
]
),
$el(
"p",
{ style: { fontSize: "10px", color: "#fff", marginLeft: "20px" } },
[
"Explicit content, including sexual content, strong violence, or intense themes. ",
]
),
$el(
"label",
{ style: { display: "flex", alignItems: "center", cursor: "pointer" } },
[
this.ratingRadioButtonsCheck_1,
$el("img", {
style: {
width: "12px",
height: "12px",
marginLeft: "5px",
},
src: "https://static.copus.io/images/client/202507/test/5c802fdcaaea4e7bbed37393eec0d5ba.png",
}),
$el("span", { style: { marginLeft: "5px", color: "#fff" } }, [
"Not Rated",
]),
]
),
$el(
"p",
{ style: { fontSize: "10px", color: "#fff", marginLeft: "20px" } },
["No age rating provided."]
),
]);
// Message Section
this.message = $el(
"div",
{
style: {
color: "#ff3d00",
textAlign: "center",
padding: "10px",
fontSize: "20px",
},
},
[]
);
this.shareButton = $el("button", {
type: "submit",
textContent: "Share",
style: buttonStyle,
onclick: () => {
this.handleShareButtonClick();
},
});
// Share and Close Buttons
const buttonsSection = $el(
"div",
{
style: {
textAlign: "right",
marginTop: "20px",
display: "flex",
justifyContent: "space-between",
},
},
[
$el("button", {
type: "button",
textContent: "Close",
style: {
...buttonStyle,
backgroundColor: undefined,
},
onclick: () => {
this.close();
},
}),
this.shareButton,
]
);
// Composing the full layout
const layout = [
headerSection,
linkSection,
accountSection,
outputUploadSection,
this.outputsSection,
additionalInputsSection,
SubtitleSection,
DescriptionSection,
// contestSection,
blockChainSection_lock,
contentRatingSection,
blockChainSection,
this.message,
buttonsSection,
];
return layout;
}
/**
* api
* @param {url} path
* @param {params} options
* @param {statusText} statusText
* @returns
*/
async fetchApi(path, options, statusText) {
if (statusText) {
this.message.textContent = statusText;
}
const fullPath = new URL(API_ENDPOINT + path);
const response = await fetch(fullPath, options);
if (!response.ok) {
throw new Error(response.statusText);
}
if (statusText) {
this.message.textContent = "";
}
const data = await response.json();
return {
ok: response.ok,
statusText: response.statusText,
status: response.status,
data,
};
}
/**
* @param {file} uploadFile
*/
async uploadThumbnail(uploadFile, type) {
const form = new FormData();
form.append("file", uploadFile);
form.append("apiToken", this.keyInput.value);
try {
const res = await this.fetchApi(
`/client/common/opus/uploadImage`,
{
method: "POST",
body: form,
},
"Uploading thumbnail..."
);
if (res.status && res.data.status && res.data) {
const { data } = res.data;
if (type) {
this.allFilesImages.push({
url: data,
});
}
this.uploadedImages.push({
url: data,
});
} else {
throw new Error(
"make sure your API key is correct and try again later"
);
}
} catch (e) {
if (e?.response?.status === 413) {
throw new Error("File size is too large (max 20MB)");
} else {
throw new Error("Error uploading thumbnail: " + e.message);
}
}
}
async handleShareButtonClick() {
this.message.textContent = "";
try {
this.shareButton.disabled = true;
this.shareButton.textContent = "Sharing...";
await this.share();
} catch (e) {
customAlert(e.message);
}
this.shareButton.disabled = false;
this.shareButton.textContent = "Share";
}
/**
* share
* @param {string} title
* @param {string} subtitle
* @param {string} content
* @param {boolean} storeOnChain
* @param {string} coverUrl
* @param {string[]} imageUrls
* @param {string} apiToken
*/
async share() {
const prompt = await app.graphToPrompt();
const workflowJSON = prompt["workflow"];
const form_values = {
title: this.TitleInput.value,
subTitle: this.SubTitleInput.value,
content: this.descriptionInput.value,
storeOnChain: this.radioButtonsCheck.checked ? true : false,
lockState: this.radioButtonsCheck_lock.checked ? 2 : 0,
unlockPrice: this.LockInput.value,
rating: this.ratingRadioButtonsCheck0.checked
? 0
: this.ratingRadioButtonsCheck1.checked
? 1
: this.ratingRadioButtonsCheck2.checked
? 2
: -1,
};
if (!this.keyInput.value) {
throw new Error("API key is required");
}
if (!this.uploadImagesInput.files[0] && !this.selectedFile) {
throw new Error("Thumbnail is required");
}
if (!form_values.title) {
throw new Error("Title is required");
}
if (this.radioButtonsCheck_lock.checked) {
if (!this.LockInput.value) {
throw new Error("Price is required");
}
}
if (!this.uploadedImages.length) {
if (this.selectedFile) {
await this.uploadThumbnail(this.selectedFile);
} else {
for (const file of this.uploadImagesInput.files) {
try {
await this.uploadThumbnail(file);
} catch (e) {
this.uploadedImages = [];
throw new Error(e.message);
}
}
if (this.uploadImagesInput.files.length === 0) {
throw new Error("No thumbnail uploaded");
}
}
}
if (this.allFiles.length > 0) {
for (const file of this.allFiles) {
try {
await this.uploadThumbnail(file, true);
} catch (e) {
this.allFilesImages = [];
throw new Error(e.message);
}
}
}
try {
const res = await this.fetchApi(
"/client/common/opus/shareFromComfyUI",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
workflowJson: workflowJSON,
apiToken: this.keyInput.value,
coverUrl: this.uploadedImages[0].url,
imageUrls: this.allFilesImages.map((image) => image.url),
...form_values,
}),
},
"Uploading workflow..."
);
if (res.status && res.data.status && res.data) {
localStorage.setItem("copus_token", this.keyInput.value);
const { data } = res.data;
if (data) {
const url = `${DEFAULT_HOMEPAGE_URL}/work/${data}`;
this.message.innerHTML = `Workflow has been shared successfully. <a href="${url}" target="_blank">Click here to view it.</a>`;
this.previewImage.src = "";
this.previewImage.style.display = "none";
this.uploadedImages = [];
this.allFilesImages = [];
this.allFiles = [];
this.TitleInput.value = "";
this.SubTitleInput.value = "";
this.descriptionInput.value = "";
this.selectedFile = null;
}
}
} catch (e) {
throw new Error("Error sharing workflow: " + e.message);
}
}
async fetchImageBlob(url) {
const response = await fetch(url);
const blob = await response.blob();
return blob;
}
async show({ potential_outputs, potential_output_nodes } = {}) {
// Sort `potential_output_nodes` by node ID to make the order always
// consistent, but we should also keep `potential_outputs` in the same
// order as `potential_output_nodes`.
const potential_output_to_order = {};
potential_output_nodes.forEach((node, index) => {
if (node.id in potential_output_to_order) {
potential_output_to_order[node.id][1].push(potential_outputs[index]);
} else {
potential_output_to_order[node.id] = [node, [potential_outputs[index]]];
}
});
// Sort the object `potential_output_to_order` by key (node ID)
const sorted_potential_output_to_order = Object.fromEntries(
Object.entries(potential_output_to_order).sort(
(a, b) => a[0].id - b[0].id
)
);
const sorted_potential_outputs = [];
const sorted_potential_output_nodes = [];
for (const [key, value] of Object.entries(
sorted_potential_output_to_order
)) {
sorted_potential_output_nodes.push(value[0]);
sorted_potential_outputs.push(...value[1]);
}
potential_output_nodes = sorted_potential_output_nodes;
potential_outputs = sorted_potential_outputs;
const apiToken = localStorage.getItem("copus_token");
this.message.innerHTML = "";
this.message.textContent = "";
this.element.style.display = "block";
this.previewImage.src = "";
this.previewImage.style.display = "none";
this.keyInput.value = apiToken != null ? apiToken : "";
this.uploadedImages = [];
this.allFilesImages = [];
this.allFiles = [];
// If `selectedNodeId` is provided, we will select the corresponding radio
// button for the node. In addition, we move the selected radio button to
// the top of the list.
if (this.selectedNodeId) {
const index = potential_output_nodes.findIndex(
(node) => node.id === this.selectedNodeId
);
if (index >= 0) {
this.selectedOutputIndex = index;
}
}
this.radioButtons = [];
const new_radio_buttons = $el(
"div",
{
id: "selectOutput-Options",
style: {
"overflow-y": "scroll",
"max-height": "200px",
display: "grid",
"grid-template-columns": "repeat(auto-fit, minmax(100px, 1fr))",
"grid-template-rows": "auto",
"grid-column-gap": "10px",
"grid-row-gap": "10px",
"margin-bottom": "10px",
padding: "10px",
"border-radius": "8px",
"box-shadow": "0 2px 4px rgba(0, 0, 0, 0.05)",
"background-color": "var(--bg-color)",
},
},
potential_outputs.map((output, index) => {
const { node_id } = output;
const radio_button = $el(
"input",
{
type: "radio",
name: "selectOutputImages",
value: index,
required: index === 0,
},
[]
);
let radio_button_img;
let filename;
if (output.type === "image" || output.type === "temp") {
radio_button_img = $el(
"img",
{
src: `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`,
style: {
width: "100px",
height: "100px",
objectFit: "cover",
borderRadius: "5px",
},
},
[]
);
filename = output.image.filename;
} else if (output.type === "output") {
radio_button_img = $el(
"img",
{
src: output.output.value,
style: {
width: "auto",
height: "100px",
objectFit: "cover",
borderRadius: "5px",
},
},
[]
);
filename = output.filename;
} else {
// unsupported output type
// this should never happen
radio_button_img = $el(
"img",
{
src: "",
style: { width: "auto", height: "100px" },
},
[]
);
}
const radio_button_text = $el(
"span",
{
style: {
color: "gray",
display: "block",
fontSize: "12px",
overflowX: "hidden",
textOverflow: "ellipsis",
textWrap: "nowrap",
maxWidth: "100px",
},
},
[output.title]
);
const node_id_chip = $el(
"span",
{
style: {
color: "#FBFBFD",
display: "block",
backgroundColor: "rgba(0, 0, 0, 0.5)",
fontSize: "12px",
overflowX: "hidden",
padding: "2px 3px",
textOverflow: "ellipsis",
textWrap: "nowrap",
maxWidth: "100px",
position: "absolute",
top: "3px",
left: "3px",
borderRadius: "3px",
},
},
[`Node: ${node_id}`]
);
radio_button.style.color = "var(--fg-color)";
radio_button.checked = this.selectedOutputIndex === index;
radio_button.onchange = async () => {
this.selectedOutputIndex = parseInt(radio_button.value);
// Remove the "checked" class from all radio buttons
this.radioButtons.forEach((ele) => {
ele.parentElement.classList.remove("checked");
});
radio_button.parentElement.classList.add("checked");
this.fetchImageBlob(radio_button_img.src).then((blob) => {
const file = new File([blob], filename, {
type: blob.type,
});
this.previewImage.src = radio_button_img.src;
this.previewImage.style.display = "block";
this.selectedFile = file;
});
// Add the opacity style toggle here to indicate that they only need
// to upload one image or choose one from the outputs.
this.outputsSection.style.opacity = 1;
this.uploadImagesInput.style.opacity = 0.35;
};
if (radio_button.checked) {
this.fetchImageBlob(radio_button_img.src).then((blob) => {
const file = new File([blob], filename, {
type: blob.type,
});
this.previewImage.src = radio_button_img.src;
this.previewImage.style.display = "block";
this.selectedFile = file;
});
// Add the opacity style toggle here to indicate that they only need
// to upload one image or choose one from the outputs.
this.outputsSection.style.opacity = 1;
this.uploadImagesInput.style.opacity = 0.35;
}
this.radioButtons.push(radio_button);
let src = "";
if (output.type === "image" || output.type === "temp") {
filename = output.image.filename;
src = `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`;
} else if (output.type === "output") {
src = output.output.value;
filename = output.filename;
}
if (src) {
this.fetchImageBlob(src).then((blob) => {
const file = new File([blob], filename, {
type: blob.type,
});
this.allFiles.push(file);
});
}
return $el(
`label.output_label${radio_button.checked ? ".checked" : ""}`,
{
style: {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
marginBottom: "10px",
cursor: "pointer",
position: "relative",
},
},
[radio_button_img, radio_button_text, radio_button, node_id_chip]
);
})
);
const header = $el(
"p",
{
textContent:
this.radioButtons.length === 0
? "Queue Prompt to see the outputs"
: "Or choose one from the outputs (scroll to see all)",
size: 2,
color: "white",
style: {
color: "white",
margin: "0 0 5px 0",
fontSize: "12px",
},
},
[]
);
this.outputsSection.innerHTML = "";
this.outputsSection.appendChild(header);
this.outputsSection.appendChild(new_radio_buttons);
}
}