Spaces:
Runtime error
Runtime error
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(); | |
} | |
} | |