import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; import { $el } from "../../scripts/ui.js"; // Dialog manager to create custom modal dialogs for FramerComfy class FramerComfyDialog { constructor() { this.dialogContainer = null; this.overlay = null; this.dialog = null; this.inputFields = []; this.resolvePromise = null; this.rejectPromise = null; this.setupStyles(); } setupStyles() { // Add custom styles for the dialog const style = document.createElement("style"); style.textContent = ` .framer-comfy-overlay { 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: 9999; } .framer-comfy-dialog { background-color: #1a1a1a; border-radius: 8px; padding: 20px; width: 80%; max-width: 600px; max-height: 80vh; overflow-y: auto; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); color: #fff; } .framer-comfy-dialog h2 { margin-top: 0; margin-bottom: 16px; font-size: 18px; border-bottom: 1px solid #333; padding-bottom: 10px; } .framer-comfy-input-group { margin-bottom: 16px; } .framer-comfy-input-label { display: block; margin-bottom: 6px; font-size: 14px; } .framer-comfy-input { width: 100%; background-color: #333; border: 1px solid #444; border-radius: 4px; padding: 8px 10px; color: #fff; font-size: 14px; } .framer-comfy-node-info { font-size: 12px; color: #999; margin-top: 4px; } .framer-comfy-buttons { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; } .framer-comfy-button { background-color: #555; color: white; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer; font-size: 14px; transition: background-color 0.2s; } .framer-comfy-button:hover { background-color: #666; } .framer-comfy-button-primary { background-color: #2d8cff; } .framer-comfy-button-primary:hover { background-color: #1a7ae8; } `; document.head.appendChild(style); } createDialogContainer() { // Create container if it doesn't exist if (!this.dialogContainer) { this.dialogContainer = document.createElement("div"); document.body.appendChild(this.dialogContainer); } return this.dialogContainer; } showModelUrlsDialog(modelInputs) { this.inputFields = []; const container = this.createDialogContainer(); return new Promise((resolve, reject) => { this.resolvePromise = resolve; this.rejectPromise = reject; // Create overlay this.overlay = document.createElement("div"); this.overlay.className = "framer-comfy-overlay"; // Prevent events from propagating to ComfyUI this.overlay.addEventListener("click", (e) => { e.stopPropagation(); }); // Stop keydown events from reaching ComfyUI this.overlay.addEventListener("keydown", (e) => { e.stopPropagation(); }); // Create dialog this.dialog = document.createElement("div"); this.dialog.className = "framer-comfy-dialog"; // Prevent clicks within dialog from propagating this.dialog.addEventListener("click", (e) => { e.stopPropagation(); }); // Dialog title const title = document.createElement("h2"); title.textContent = "Enter Information for FramerComfy"; this.dialog.appendChild(title); // Create form content const form = document.createElement("form"); form.onsubmit = (e) => { e.preventDefault(); e.stopPropagation(); this.handleSubmit(); }; // Prevent paste events from propagating form.addEventListener("paste", (e) => { e.stopPropagation(); }); // Handle keyboard events at the form level form.addEventListener("keydown", (e) => { e.stopPropagation(); }); // Add required workflow inputs const requiredInputsGroup = document.createElement("div"); requiredInputsGroup.className = "framer-comfy-input-group"; // Workflow Name input const workflowNameLabel = document.createElement("label"); workflowNameLabel.className = "framer-comfy-input-label"; workflowNameLabel.textContent = "Workflow Name (required)"; requiredInputsGroup.appendChild(workflowNameLabel); const workflowNameInput = document.createElement("input"); workflowNameInput.className = "framer-comfy-input"; workflowNameInput.type = "text"; workflowNameInput.placeholder = "Enter workflow name"; workflowNameInput.required = true; workflowNameInput.dataset.inputType = "workflow_name"; // Add event listeners to prevent propagation workflowNameInput.addEventListener("paste", (e) => e.stopPropagation()); workflowNameInput.addEventListener("keydown", (e) => e.stopPropagation()); workflowNameInput.addEventListener("focus", (e) => e.stopPropagation()); workflowNameInput.addEventListener("blur", (e) => e.stopPropagation()); this.inputFields.push(workflowNameInput); requiredInputsGroup.appendChild(workflowNameInput); // Hugging Face Access Token input const tokenLabel = document.createElement("label"); tokenLabel.className = "framer-comfy-input-label"; tokenLabel.textContent = "Hugging Face Access Token (required)"; tokenLabel.style.marginTop = "16px"; requiredInputsGroup.appendChild(tokenLabel); const tokenInput = document.createElement("input"); tokenInput.className = "framer-comfy-input"; tokenInput.type = "password"; tokenInput.placeholder = "Enter your Hugging Face access token"; tokenInput.required = true; tokenInput.dataset.inputType = "huggingface_access_token"; // Add event listeners to prevent propagation tokenInput.addEventListener("paste", (e) => e.stopPropagation()); tokenInput.addEventListener("keydown", (e) => e.stopPropagation()); tokenInput.addEventListener("focus", (e) => e.stopPropagation()); tokenInput.addEventListener("blur", (e) => e.stopPropagation()); this.inputFields.push(tokenInput); requiredInputsGroup.appendChild(tokenInput); form.appendChild(requiredInputsGroup); // Add a separator const separator = document.createElement("hr"); separator.style.margin = "20px 0"; separator.style.borderColor = "#333"; form.appendChild(separator); // Model inputs section title if there are any if (modelInputs.length > 0) { const modelSectionTitle = document.createElement("h3"); modelSectionTitle.textContent = "Model URLs"; modelSectionTitle.style.fontSize = "16px"; modelSectionTitle.style.marginBottom = "16px"; form.appendChild(modelSectionTitle); } // Add inputs for each model modelInputs.forEach((input, index) => { const { nodeId, nodeTitle, inputName, promptMessage } = input; const inputGroup = document.createElement("div"); inputGroup.className = "framer-comfy-input-group"; const label = document.createElement("label"); label.className = "framer-comfy-input-label"; label.textContent = promptMessage; inputGroup.appendChild(label); const inputField = document.createElement("input"); inputField.className = "framer-comfy-input"; inputField.type = "text"; inputField.placeholder = "https://huggingface.co/..."; inputField.dataset.nodeId = nodeId; inputField.dataset.inputName = inputName; inputField.dataset.inputType = "model_url"; // Add event listeners to prevent propagation inputField.addEventListener("paste", (e) => e.stopPropagation()); inputField.addEventListener("keydown", (e) => e.stopPropagation()); inputField.addEventListener("focus", (e) => e.stopPropagation()); inputField.addEventListener("blur", (e) => e.stopPropagation()); this.inputFields.push(inputField); inputGroup.appendChild(inputField); const nodeInfo = document.createElement("div"); nodeInfo.className = "framer-comfy-node-info"; nodeInfo.textContent = `Node: ${nodeTitle} (ID: ${nodeId})`; inputGroup.appendChild(nodeInfo); form.appendChild(inputGroup); }); // Action buttons const buttonsContainer = document.createElement("div"); buttonsContainer.className = "framer-comfy-buttons"; const cancelButton = document.createElement("button"); cancelButton.className = "framer-comfy-button"; cancelButton.textContent = "Cancel"; cancelButton.type = "button"; cancelButton.onclick = (e) => { e.stopPropagation(); this.handleCancel(); }; buttonsContainer.appendChild(cancelButton); const submitButton = document.createElement("button"); submitButton.className = "framer-comfy-button framer-comfy-button-primary"; submitButton.textContent = "Submit"; submitButton.type = "submit"; submitButton.addEventListener("click", (e) => e.stopPropagation()); buttonsContainer.appendChild(submitButton); form.appendChild(buttonsContainer); this.dialog.appendChild(form); // Append dialog to overlay this.overlay.appendChild(this.dialog); // Add to container container.appendChild(this.overlay); }); } handleSubmit() { // Validate required fields const requiredInputs = this.inputFields.filter(input => input.dataset.inputType === "workflow_name" || input.dataset.inputType === "huggingface_access_token" ); for (const input of requiredInputs) { if (!input.value.trim()) { alert(`${input.dataset.inputType === "workflow_name" ? "Workflow Name" : "Hugging Face Access Token"} is required.`); return; } } // Extract required workflow information const workflowInfo = { workflow_name: "", huggingface_access_token: "" }; // Collect values from all input fields const modelResults = []; for (const input of this.inputFields) { const value = input.value.trim(); if (input.dataset.inputType === "workflow_name") { workflowInfo.workflow_name = value; } else if (input.dataset.inputType === "huggingface_access_token") { workflowInfo.huggingface_access_token = value; } else if (input.dataset.inputType === "model_url" && value) { modelResults.push({ nodeId: input.dataset.nodeId, inputName: input.dataset.inputName, value: value }); } } this.closeDialog(); this.resolvePromise({ workflowInfo: workflowInfo, modelResults: modelResults }); } handleCancel() { this.closeDialog(); this.resolvePromise([]); } closeDialog() { if (this.overlay && this.dialogContainer) { this.dialogContainer.removeChild(this.overlay); this.overlay = null; this.dialog = null; } } } // Export the dialog manager export const framerComfyDialog = new FramerComfyDialog();