/** * RM Styles Editor Modal * Provides a modal dialog for editing the styles.json file */ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; // Create and inject CSS styles - ComfyUI theme const styleSheet = document.createElement("style"); styleSheet.textContent = ` .rm-styles-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(4px); z-index: 10000; display: flex; align-items: center; justify-content: center; } .rm-styles-modal { background: #202020; border-radius: 8px; width: 90%; max-width: 900px; max-height: 85vh; display: flex; flex-direction: column; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6); border: 1px solid #444; } .rm-styles-modal-header { display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1rem; border-bottom: 1px solid #444; background: #2a2a2a; border-radius: 8px 8px 0 0; } .rm-styles-modal-header h2 { margin: 0; font-size: 1rem; color: #ddd; font-weight: 500; } .rm-styles-modal-close { background: none; border: none; font-size: 1.25rem; color: #888; cursor: pointer; padding: 0.25rem; line-height: 1; } .rm-styles-modal-close:hover { color: #fff; } .rm-styles-modal-content { flex: 1; overflow-y: auto; padding: 1rem; background: #1a1a1a; } .rm-styles-toolbar { display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; } .rm-styles-toolbar button { padding: 0.4rem 0.75rem; border: 1px solid #555; border-radius: 4px; background: #353535; color: #ddd; cursor: pointer; font-size: 0.8rem; transition: all 0.15s ease; } .rm-styles-toolbar button:hover { background: #454545; border-color: #666; } .rm-styles-toolbar .rm-btn-import { background: #2d4a3e; border-color: #3d6a5e; } .rm-styles-toolbar .rm-btn-import:hover { background: #3d5a4e; border-color: #4d7a6e; } .rm-styles-list { display: flex; flex-direction: column; gap: 2px; } .rm-style-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 0.75rem; background: #2a2a2a; border-radius: 4px; cursor: pointer; border: 1px solid transparent; transition: all 0.1s ease; } .rm-style-item:hover { background: #353535; border-color: #555; } .rm-style-item.selected { background: #3a3a3a; border-color: #6a9; } .rm-style-number { font-weight: 600; color: #6a9; min-width: 35px; font-size: 0.85rem; } .rm-style-name { flex: 1; color: #ddd; font-size: 0.85rem; } .rm-style-preview { color: #777; font-size: 0.7rem; max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .rm-style-editor { margin-top: 1rem; padding: 1rem; background: #252525; border-radius: 6px; border: 1px solid #444; } .rm-style-editor-row { display: flex; flex-direction: column; gap: 0.25rem; margin-bottom: 0.75rem; } .rm-style-editor-row label { color: #999; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.5px; } .rm-style-editor-row input, .rm-style-editor-row textarea { padding: 0.5rem; border: 1px solid #444; border-radius: 4px; background: #1a1a1a; color: #ddd; font-family: inherit; font-size: 0.85rem; transition: border-color 0.15s ease; } .rm-style-editor-row textarea { min-height: 70px; resize: vertical; } .rm-style-editor-row input:focus, .rm-style-editor-row textarea:focus { outline: none; border-color: #6a9; } .rm-loras-section { margin-top: 0.75rem; padding: 0.75rem; background: #1e1e1e; border-radius: 4px; border: 1px solid #333; } .rm-loras-section h4 { margin: 0 0 0.5rem 0; color: #888; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.5px; } .rm-lora-item { display: flex; gap: 0.5rem; margin-bottom: 0.4rem; align-items: center; } .rm-lora-item input { flex: 1; padding: 0.35rem; border: 1px solid #444; border-radius: 3px; background: #2a2a2a; color: #ddd; font-size: 0.75rem; } .rm-lora-item input[type="number"] { width: 55px; flex: none; } .rm-lora-remove { background: #6a3333; border: 1px solid #8a4444; color: #ddd; padding: 0.2rem 0.4rem; border-radius: 3px; cursor: pointer; font-size: 0.7rem; transition: all 0.15s ease; } .rm-lora-remove:hover { background: #8a4444; } .rm-lora-add { background: #2d4a3e; border: 1px solid #3d6a5e; color: #ddd; padding: 0.25rem 0.5rem; border-radius: 3px; cursor: pointer; font-size: 0.7rem; margin-top: 0.4rem; transition: all 0.15s ease; } .rm-lora-add:hover { background: #3d5a4e; } .rm-style-actions { display: flex; gap: 0.5rem; margin-top: 1rem; justify-content: flex-end; } .rm-style-actions button { padding: 0.4rem 0.75rem; border: none; border-radius: 4px; cursor: pointer; font-size: 0.8rem; transition: all 0.15s ease; } .rm-btn-save { background: #2d4a3e; border: 1px solid #3d6a5e; color: #ddd; } .rm-btn-save:hover { background: #3d5a4e; } .rm-btn-delete { background: #6a3333; border: 1px solid #8a4444; color: #ddd; } .rm-btn-delete:hover { background: #8a4444; } .rm-btn-cancel { background: #353535; border: 1px solid #555; color: #ddd; } .rm-btn-cancel:hover { background: #454545; } /* Import dialog styles */ .rm-import-dialog-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); z-index: 10001; display: flex; align-items: center; justify-content: center; } .rm-import-dialog { background: #252525; border-radius: 8px; padding: 1.25rem; border: 1px solid #555; min-width: 320px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); } .rm-import-dialog h3 { margin: 0 0 0.75rem 0; color: #ddd; font-size: 0.95rem; font-weight: 500; } .rm-import-dialog p { color: #999; font-size: 0.8rem; margin: 0 0 1rem 0; } .rm-import-dialog-buttons { display: flex; gap: 0.5rem; justify-content: flex-end; } .rm-import-dialog-buttons button { padding: 0.4rem 0.75rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; transition: all 0.15s ease; } .rm-import-replace { background: #5a4a2a; border: 1px solid #7a6a4a; color: #ddd; } .rm-import-replace:hover { background: #6a5a3a; } .rm-import-append { background: #2d4a3e; border: 1px solid #3d6a5e; color: #ddd; } .rm-import-append:hover { background: #3d5a4e; } .rm-import-cancel { background: #353535; border: 1px solid #555; color: #ddd; } .rm-import-cancel:hover { background: #454545; } /* Hidden file input */ .rm-file-input { display: none; } `; document.head.appendChild(styleSheet); class RMStylesEditorModal { constructor(customPath = "") { this.styles = []; this.selectedStyle = null; this.overlay = null; this.customPath = customPath; } _apiUrl(path) { if (this.customPath) { const sep = path.includes("?") ? "&" : "?"; return `${path}${sep}path=${encodeURIComponent(this.customPath)}`; } return path; } async open() { await this.loadStyles(); this.render(); } close() { if (this.overlay) { this.overlay.remove(); this.overlay = null; } } async loadStyles() { try { const response = await api.fetchApi(this._apiUrl("/api/rmautomation/styles")); const data = await response.json(); this.styles = data.styles || []; } catch (e) { console.error("Failed to load styles:", e); this.styles = []; } } async saveStyle(style) { try { const response = await api.fetchApi(this._apiUrl(`/api/rmautomation/styles/${style.number}`), { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(style), }); if (response.ok) { await this.loadStyles(); this.renderStylesList(); } } catch (e) { console.error("Failed to save style:", e); } } async deleteStyle(number) { if (!confirm(`Delete style #${number}?`)) return; try { const response = await api.fetchApi(this._apiUrl(`/api/rmautomation/styles/${number}`), { method: "DELETE", }); if (response.ok) { this.selectedStyle = null; await this.loadStyles(); this.renderStylesList(); this.renderEditor(); } } catch (e) { console.error("Failed to delete style:", e); } } render() { this.overlay = document.createElement("div"); this.overlay.className = "rm-styles-modal-overlay"; this.overlay.onclick = (e) => { if (e.target === this.overlay) this.close(); }; const headerLabel = this.customPath ? `RM Styles Editor - ${this.customPath.split(/[/\\]/).pop()}` : "RM Styles Editor"; const modal = document.createElement("div"); modal.className = "rm-styles-modal"; modal.innerHTML = `
Found ${importedStyles.length} style(s) to import. How would you like to proceed?
`; dialog.querySelector(".rm-import-cancel").onclick = () => { dialogOverlay.remove(); }; dialog.querySelector(".rm-import-replace").onclick = async () => { dialogOverlay.remove(); await this.importStyles(importedStyles, "replace"); }; dialog.querySelector(".rm-import-append").onclick = async () => { dialogOverlay.remove(); await this.importStyles(importedStyles, "append"); }; dialogOverlay.appendChild(dialog); document.body.appendChild(dialogOverlay); } async importStyles(importedStyles, mode) { try { const response = await api.fetchApi(this._apiUrl("/api/rmautomation/styles/import"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ styles: importedStyles, mode: mode }), }); if (response.ok) { await this.loadStyles(); this.selectedStyle = null; this.renderStylesList(); this.renderEditor(); } else { const error = await response.text(); console.error("Import failed:", error); alert("Failed to import styles. Check console for details."); } } catch (e) { console.error("Failed to import styles:", e); alert("Failed to import styles. Check console for details."); } } } // Register the button on RM Styles nodes app.registerExtension({ name: "RMAutomation.StylesEditor", async beforeRegisterNodeDef(nodeType, nodeData, app) { if (nodeData.name === "RMStylesFull" || nodeData.name === "RMStylesFullDisplay" || nodeData.name === "RMStylesPipe") { const onNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function () { onNodeCreated?.apply(this, arguments); // Add "Edit Styles" button const node = this; const editBtn = this.addWidget("button", "Edit Styles", null, () => { const pathWidget = node.widgets?.find(w => w.name === "custom_json_path"); const customPath = pathWidget?.value?.trim() || ""; const modal = new RMStylesEditorModal(customPath); modal.open(); }); }; } }, });