Spaces:
Paused
Paused
| import { $el } from "../ui.js"; | |
| import { api } from "../api.js"; | |
| import { ComfyDialog } from "./dialog.js"; | |
| export class ComfySettingsDialog extends ComfyDialog { | |
| constructor(app) { | |
| super(); | |
| this.app = app; | |
| this.settingsValues = {}; | |
| this.settingsLookup = {}; | |
| this.element = $el( | |
| "dialog", | |
| { | |
| id: "comfy-settings-dialog", | |
| parent: document.body, | |
| }, | |
| [ | |
| $el("table.comfy-modal-content.comfy-table", [ | |
| $el( | |
| "caption", | |
| { textContent: "Settings" }, | |
| $el("button.comfy-btn", { | |
| type: "button", | |
| textContent: "\u00d7", | |
| onclick: () => { | |
| this.element.close(); | |
| }, | |
| }) | |
| ), | |
| $el("tbody", { $: (tbody) => (this.textElement = tbody) }), | |
| $el("button", { | |
| type: "button", | |
| textContent: "Close", | |
| style: { | |
| cursor: "pointer", | |
| }, | |
| onclick: () => { | |
| this.element.close(); | |
| }, | |
| }), | |
| ]), | |
| ] | |
| ); | |
| } | |
| get settings() { | |
| return Object.values(this.settingsLookup); | |
| } | |
| #dispatchChange(id, value, oldValue) { | |
| this.dispatchEvent( | |
| new CustomEvent(id + ".change", { | |
| detail: { | |
| value, | |
| oldValue | |
| }, | |
| }) | |
| ); | |
| } | |
| async load() { | |
| if (this.app.storageLocation === "browser") { | |
| this.settingsValues = localStorage; | |
| } else { | |
| this.settingsValues = await api.getSettings(); | |
| } | |
| // Trigger onChange for any settings added before load | |
| for (const id in this.settingsLookup) { | |
| const value = this.settingsValues[this.getId(id)]; | |
| this.settingsLookup[id].onChange?.(value); | |
| this.#dispatchChange(id, value); | |
| } | |
| } | |
| getId(id) { | |
| if (this.app.storageLocation === "browser") { | |
| id = "Comfy.Settings." + id; | |
| } | |
| return id; | |
| } | |
| getSettingValue(id, defaultValue) { | |
| let value = this.settingsValues[this.getId(id)]; | |
| if(value != null) { | |
| if(this.app.storageLocation === "browser") { | |
| try { | |
| value = JSON.parse(value); | |
| } catch (error) { | |
| } | |
| } | |
| } | |
| return value ?? defaultValue; | |
| } | |
| async setSettingValueAsync(id, value) { | |
| const json = JSON.stringify(value); | |
| localStorage["Comfy.Settings." + id] = json; // backwards compatibility for extensions keep setting in storage | |
| let oldValue = this.getSettingValue(id, undefined); | |
| this.settingsValues[this.getId(id)] = value; | |
| if (id in this.settingsLookup) { | |
| this.settingsLookup[id].onChange?.(value, oldValue); | |
| } | |
| this.#dispatchChange(id, value, oldValue); | |
| await api.storeSetting(id, value); | |
| } | |
| setSettingValue(id, value) { | |
| this.setSettingValueAsync(id, value).catch((err) => { | |
| alert(`Error saving setting '${id}'`); | |
| console.error(err); | |
| }); | |
| } | |
| addSetting({ id, name, type, defaultValue, onChange, attrs = {}, tooltip = "", options = undefined }) { | |
| if (!id) { | |
| throw new Error("Settings must have an ID"); | |
| } | |
| if (id in this.settingsLookup) { | |
| throw new Error(`Setting ${id} of type ${type} must have a unique ID.`); | |
| } | |
| let skipOnChange = false; | |
| let value = this.getSettingValue(id); | |
| if (value == null) { | |
| if (this.app.isNewUserSession) { | |
| // Check if we have a localStorage value but not a setting value and we are a new user | |
| const localValue = localStorage["Comfy.Settings." + id]; | |
| if (localValue) { | |
| value = JSON.parse(localValue); | |
| this.setSettingValue(id, value); // Store on the server | |
| } | |
| } | |
| if (value == null) { | |
| value = defaultValue; | |
| } | |
| } | |
| // Trigger initial setting of value | |
| if (!skipOnChange) { | |
| onChange?.(value, undefined); | |
| } | |
| this.settingsLookup[id] = { | |
| id, | |
| onChange, | |
| name, | |
| render: () => { | |
| if (type === "hidden") return; | |
| const setter = (v) => { | |
| if (onChange) { | |
| onChange(v, value); | |
| } | |
| this.setSettingValue(id, v); | |
| value = v; | |
| }; | |
| value = this.getSettingValue(id, defaultValue); | |
| let element; | |
| const htmlID = id.replaceAll(".", "-"); | |
| const labelCell = $el("td", [ | |
| $el("label", { | |
| for: htmlID, | |
| classList: [tooltip !== "" ? "comfy-tooltip-indicator" : ""], | |
| textContent: name, | |
| }), | |
| ]); | |
| if (typeof type === "function") { | |
| element = type(name, setter, value, attrs); | |
| } else { | |
| switch (type) { | |
| case "boolean": | |
| element = $el("tr", [ | |
| labelCell, | |
| $el("td", [ | |
| $el("input", { | |
| id: htmlID, | |
| type: "checkbox", | |
| checked: value, | |
| onchange: (event) => { | |
| const isChecked = event.target.checked; | |
| if (onChange !== undefined) { | |
| onChange(isChecked); | |
| } | |
| this.setSettingValue(id, isChecked); | |
| }, | |
| }), | |
| ]), | |
| ]); | |
| break; | |
| case "number": | |
| element = $el("tr", [ | |
| labelCell, | |
| $el("td", [ | |
| $el("input", { | |
| type, | |
| value, | |
| id: htmlID, | |
| oninput: (e) => { | |
| setter(e.target.value); | |
| }, | |
| ...attrs, | |
| }), | |
| ]), | |
| ]); | |
| break; | |
| case "slider": | |
| element = $el("tr", [ | |
| labelCell, | |
| $el("td", [ | |
| $el( | |
| "div", | |
| { | |
| style: { | |
| display: "grid", | |
| gridAutoFlow: "column", | |
| }, | |
| }, | |
| [ | |
| $el("input", { | |
| ...attrs, | |
| value, | |
| type: "range", | |
| oninput: (e) => { | |
| setter(e.target.value); | |
| e.target.nextElementSibling.value = e.target.value; | |
| }, | |
| }), | |
| $el("input", { | |
| ...attrs, | |
| value, | |
| id: htmlID, | |
| type: "number", | |
| style: { maxWidth: "4rem" }, | |
| oninput: (e) => { | |
| setter(e.target.value); | |
| e.target.previousElementSibling.value = e.target.value; | |
| }, | |
| }), | |
| ] | |
| ), | |
| ]), | |
| ]); | |
| break; | |
| case "combo": | |
| element = $el("tr", [ | |
| labelCell, | |
| $el("td", [ | |
| $el( | |
| "select", | |
| { | |
| oninput: (e) => { | |
| setter(e.target.value); | |
| }, | |
| }, | |
| (typeof options === "function" ? options(value) : options || []).map((opt) => { | |
| if (typeof opt === "string") { | |
| opt = { text: opt }; | |
| } | |
| const v = opt.value ?? opt.text; | |
| return $el("option", { | |
| value: v, | |
| textContent: opt.text, | |
| selected: value + "" === v + "", | |
| }); | |
| }) | |
| ), | |
| ]), | |
| ]); | |
| break; | |
| case "text": | |
| default: | |
| if (type !== "text") { | |
| console.warn(`Unsupported setting type '${type}, defaulting to text`); | |
| } | |
| element = $el("tr", [ | |
| labelCell, | |
| $el("td", [ | |
| $el("input", { | |
| value, | |
| id: htmlID, | |
| oninput: (e) => { | |
| setter(e.target.value); | |
| }, | |
| ...attrs, | |
| }), | |
| ]), | |
| ]); | |
| break; | |
| } | |
| } | |
| if (tooltip) { | |
| element.title = tooltip; | |
| } | |
| return element; | |
| }, | |
| }; | |
| const self = this; | |
| return { | |
| get value() { | |
| return self.getSettingValue(id, defaultValue); | |
| }, | |
| set value(v) { | |
| self.setSettingValue(id, v); | |
| }, | |
| }; | |
| } | |
| show() { | |
| this.textElement.replaceChildren( | |
| $el( | |
| "tr", | |
| { | |
| style: { display: "none" }, | |
| }, | |
| [$el("th"), $el("th", { style: { width: "33%" } })] | |
| ), | |
| ...this.settings.sort((a, b) => a.name.localeCompare(b.name)).map((s) => s.render()).filter(Boolean) | |
| ); | |
| this.element.showModal(); | |
| } | |
| } | |