| | 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); |
| | } |
| |
|
| | async load() { |
| | if (this.app.storageLocation === "browser") { |
| | this.settingsValues = localStorage; |
| | } else { |
| | this.settingsValues = await api.getSettings(); |
| | } |
| |
|
| | |
| | for (const id in this.settingsLookup) { |
| | this.settingsLookup[id].onChange?.(this.settingsValues[this.getId(id)]); |
| | } |
| | } |
| |
|
| | 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; |
| |
|
| | let oldValue = this.getSettingValue(id, undefined); |
| | this.settingsValues[this.getId(id)] = value; |
| |
|
| | if (id in this.settingsLookup) { |
| | this.settingsLookup[id].onChange?.(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) { |
| | |
| | const localValue = localStorage["Comfy.Settings." + id]; |
| | if (localValue) { |
| | value = JSON.parse(localValue); |
| | this.setSettingValue(id, value); |
| | } |
| | } |
| | if (value == null) { |
| | value = defaultValue; |
| | } |
| | } |
| |
|
| | |
| | if (!skipOnChange) { |
| | onChange?.(value, undefined); |
| | } |
| |
|
| | this.settingsLookup[id] = { |
| | id, |
| | onChange, |
| | name, |
| | render: () => { |
| | 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()) |
| | ); |
| | this.element.showModal(); |
| | } |
| | } |
| |
|