Merge branch 'beta' into pause

This commit is contained in:
JeLuF 2022-12-11 11:19:48 +01:00
commit e6346775e7
8 changed files with 124 additions and 65 deletions

View File

@ -27,6 +27,7 @@
- Support loading models in the safetensor format, for improved safety - Support loading models in the safetensor format, for improved safety
### Detailed changelog ### Detailed changelog
* 2.4.19 - 10 Dec 2022 - Show init img in task list
* 2.4.19 - 7 Dec 2022 - Use pre-trained hypernetworks while generating images. Thanks @C0bra5 * 2.4.19 - 7 Dec 2022 - Use pre-trained hypernetworks while generating images. Thanks @C0bra5
* 2.4.19 - 6 Dec 2022 - Allow processing new tasks first. Thanks @madrang * 2.4.19 - 6 Dec 2022 - Allow processing new tasks first. Thanks @madrang
* 2.4.19 - 6 Dec 2022 - Allow reordering the task queue (by dragging tasks). Thanks @madrang * 2.4.19 - 6 Dec 2022 - Allow reordering the task queue (by dragging tasks). Thanks @madrang

View File

@ -57,7 +57,7 @@
<input id="prompt_from_file" name="prompt_from_file" type="file" /> <!-- hidden --> <input id="prompt_from_file" name="prompt_from_file" type="file" /> <!-- hidden -->
<label for="negative_prompt" class="collapsible" id="negative_prompt_handle"> <label for="negative_prompt" class="collapsible" id="negative_prompt_handle">
Negative Prompt Negative Prompt
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Writing-prompts#negative-prompts" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip right">Click to learn more about Negative Prompts</span></i></a> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Writing-prompts#negative-prompts" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about Negative Prompts</span></i></a>
<small>(optional)</small> <small>(optional)</small>
</label> </label>
<div class="collapsible-content"> <div class="collapsible-content">
@ -97,7 +97,7 @@
</div> </div>
<div id="editor-inputs-tags-container" class="row"> <div id="editor-inputs-tags-container" class="row">
<label>Image Modifiers <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip right">click an Image Modifier to remove it, use Ctrl+Mouse Wheel to adjust its weight</span></i>:</label> <label>Image Modifiers <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">click an Image Modifier to remove it, use Ctrl+Mouse Wheel to adjust its weight</span></i>:</label>
<div id="editor-inputs-tags-list"></div> <div id="editor-inputs-tags-list"></div>
</div> </div>
@ -111,7 +111,7 @@
<h4 class="collapsible"> <h4 class="collapsible">
Image Settings Image Settings
<i id="reset-image-settings" class="fa-solid fa-arrow-rotate-left section-button"> <i id="reset-image-settings" class="fa-solid fa-arrow-rotate-left section-button">
<span class="simple-tooltip right"> <span class="simple-tooltip top-left">
Reset Image Settings Reset Image Settings
</span> </span>
</i> </i>
@ -125,13 +125,13 @@
<select id="stable_diffusion_model" name="stable_diffusion_model"> <select id="stable_diffusion_model" name="stable_diffusion_model">
<!-- <option value="sd-v1-4" selected>sd-v1-4</option> --> <!-- <option value="sd-v1-4" selected>sd-v1-4</option> -->
</select> </select>
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Custom-Models" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip right">Click to learn more about custom models</span></i></a> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Custom-Models" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about custom models</span></i></a>
</td></tr> </td></tr>
<tr class="pl-5"><td><label for="vae_model">Custom VAE:</i></label></td><td> <tr class="pl-5"><td><label for="vae_model">Custom VAE:</i></label></td><td>
<select id="vae_model" name="vae_model"> <select id="vae_model" name="vae_model">
<!-- <option value="" selected>None</option> --> <!-- <option value="" selected>None</option> -->
</select> </select>
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/VAE-Variational-Auto-Encoder" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip right">Click to learn more about VAEs</span></i></a> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/VAE-Variational-Auto-Encoder" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about VAEs</span></i></a>
</td></tr> </td></tr>
<tr class="pl-5"><td><label for="hypernetwork_model">Hypernetwork:</i></label></td><td> <tr class="pl-5"><td><label for="hypernetwork_model">Hypernetwork:</i></label></td><td>
<select id="hypernetwork_model" name="hypernetwork_model"> <select id="hypernetwork_model" name="hypernetwork_model">
@ -150,7 +150,7 @@
<option value="dpm2_a">dpm2_a</option> <option value="dpm2_a">dpm2_a</option>
<option value="lms">lms</option> <option value="lms">lms</option>
</select> </select>
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/How-to-Use#samplers" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip right">Click to learn more about samplers</span></i></a> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/How-to-Use#samplers" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about samplers</span></i></a>
</td></tr> </td></tr>
<tr class="pl-5"><td><label>Image Size: </label></td><td> <tr class="pl-5"><td><label>Image Size: </label></td><td>
<select id="width" name="width" value="512"> <select id="width" name="width" value="512">
@ -280,7 +280,7 @@
<tr><td><label>Compatible Graphics Cards (all):</label></td><td id="system-info-gpus-all" class="value"></td></tr> <tr><td><label>Compatible Graphics Cards (all):</label></td><td id="system-info-gpus-all" class="value"></td></tr>
<tr><td></td><td>&nbsp;</td></tr> <tr><td></td><td>&nbsp;</td></tr>
<tr><td><label>Used for rendering 🔥:</label></td><td id="system-info-rendering-devices" class="value"></td></tr> <tr><td><label>Used for rendering 🔥:</label></td><td id="system-info-rendering-devices" class="value"></td></tr>
<tr><td><label>Server Addresses <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip right">You can access Stable Diffusion UI from other devices using these addresses</span></i> :</label></td><td id="system-info-server-hosts" class="value"></td></tr> <tr><td><label>Server Addresses <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">You can access Stable Diffusion UI from other devices using these addresses</span></i> :</label></td><td id="system-info-server-hosts" class="value"></td></tr>
</table> </table>
</div> </div>
</div> </div>

View File

@ -937,6 +937,15 @@ input::file-selector-button {
transform: translate(-50%, 100%); transform: translate(-50%, 100%);
} }
.simple-tooltip.top-left {
top: 0px;
left: 0px;
transform: translate(calc(-100% + 15%), calc(-100% + 15%));
}
:hover > .simple-tooltip.top-left {
transform: translate(-80%, -100%);
}
/* PROGRESS BAR */ /* PROGRESS BAR */
.progress-bar { .progress-bar {
background: var(--background-color3); background: var(--background-color3);
@ -945,6 +954,7 @@ input::file-selector-button {
height: 16px; height: 16px;
position: relative; position: relative;
transition: 0.25s 1s border, 0.25s 1s height; transition: 0.25s 1s border, 0.25s 1s height;
clear: both;
} }
.progress-bar > div { .progress-bar > div {
background: var(--accent-color); background: var(--accent-color);
@ -1096,6 +1106,15 @@ button:active {
left: 1px; left: 1px;
} }
div.task-initimg > img {
margin-right: 6px;
display: block;
}
div.task-fs-initimage {
display: none;
position: absolute;
}
button#save-system-settings-btn { button#save-system-settings-btn {
padding: 4pt 8pt; padding: 4pt 8pt;
} }

View File

@ -465,7 +465,7 @@ function checkReadTextClipboardPermission (result) {
// PASTE ICON // PASTE ICON
const pasteIcon = document.createElement('i') const pasteIcon = document.createElement('i')
pasteIcon.className = 'fa-solid fa-paste section-button' pasteIcon.className = 'fa-solid fa-paste section-button'
pasteIcon.innerHTML = `<span class="simple-tooltip right">Paste Image Settings</span>` pasteIcon.innerHTML = `<span class="simple-tooltip top-left">Paste Image Settings</span>`
pasteIcon.addEventListener('click', async (event) => { pasteIcon.addEventListener('click', async (event) => {
event.stopPropagation() event.stopPropagation()
// Add css class 'active' // Add css class 'active'
@ -505,7 +505,7 @@ function checkWriteToClipboardPermission (result) {
// COPY ICON // COPY ICON
const copyIcon = document.createElement('i') const copyIcon = document.createElement('i')
copyIcon.className = 'fa-solid fa-clipboard section-button' copyIcon.className = 'fa-solid fa-clipboard section-button'
copyIcon.innerHTML = `<span class="simple-tooltip right">Copy Image Settings</span>` copyIcon.innerHTML = `<span class="simple-tooltip top-left">Copy Image Settings</span>`
copyIcon.addEventListener('click', (event) => { copyIcon.addEventListener('click', (event) => {
event.stopPropagation() event.stopPropagation()
// Add css class 'active' // Add css class 'active'

View File

@ -196,7 +196,7 @@
const eventSource = new GenericEventSource(EVENTS_TYPES) const eventSource = new GenericEventSource(EVENTS_TYPES)
function setServerStatus(msgType, msg) { function setServerStatus(msgType, msg) {
eventSource.fireEvent(EVENT_STATUS_CHANGED, {type: msgType, message: msg}) return eventSource.fireEvent(EVENT_STATUS_CHANGED, {type: msgType, message: msg})
} }
const ServerStates = { const ServerStates = {
@ -628,7 +628,7 @@
} }
this._setStatus(TaskStatus.pending) this._setStatus(TaskStatus.pending)
task_queue.set(this, promiseGenerator) task_queue.set(this, promiseGenerator)
eventSource.fireEvent(EVENT_TASK_QUEUED, {task:this}) await eventSource.fireEvent(EVENT_TASK_QUEUED, {task:this})
await Task.enqueue(promiseGenerator, ...args) await Task.enqueue(promiseGenerator, ...args)
await this.waitUntil({status: TaskStatus.completed}) await this.waitUntil({status: TaskStatus.completed})
if (this.exception) { if (this.exception) {
@ -846,7 +846,7 @@
if (typeof jsonResponse?.task !== 'number') { if (typeof jsonResponse?.task !== 'number') {
console.warn('Endpoint error response: ', jsonResponse) console.warn('Endpoint error response: ', jsonResponse)
const event = Object.assign({task:this}, jsonResponse) const event = Object.assign({task:this}, jsonResponse)
eventSource.fireEvent(EVENT_UNEXPECTED_RESPONSE, event) await eventSource.fireEvent(EVENT_UNEXPECTED_RESPONSE, event)
if ('continueWith' in event) { if ('continueWith' in event) {
jsonResponse = await Promise.resolve(event.continueWith) jsonResponse = await Promise.resolve(event.continueWith)
} }
@ -1084,12 +1084,13 @@
function getServerCapacity() { function getServerCapacity() {
let activeDevicesCount = Object.keys(serverState?.devices?.active || {}).length let activeDevicesCount = Object.keys(serverState?.devices?.active || {}).length
if (window.document.visibilityState === 'hidden') { if (typeof window === "object" && window.document.visibilityState === 'hidden') {
activeDevicesCount = 1 + activeDevicesCount activeDevicesCount = 1 + activeDevicesCount
} }
return activeDevicesCount return activeDevicesCount
} }
let idleEventPromise = undefined
function continueTasks() { function continueTasks() {
if (typeof navigator?.scheduling?.isInputPending === 'function') { if (typeof navigator?.scheduling?.isInputPending === 'function') {
const inputPendingOptions = { const inputPendingOptions = {
@ -1104,14 +1105,18 @@
} }
const serverCapacity = getServerCapacity() const serverCapacity = getServerCapacity()
if (task_queue.size <= 0 && concurrent_generators.size <= 0) { if (task_queue.size <= 0 && concurrent_generators.size <= 0) {
eventSource.fireEvent(EVENT_IDLE, {capacity: serverCapacity, idle: true}) if (!idleEventPromise?.isPending) {
idleEventPromise = makeQuerablePromise(eventSource.fireEvent(EVENT_IDLE, {capacity: serverCapacity, idle: true}))
}
// Calling idle could result in task being added to queue. // Calling idle could result in task being added to queue.
if (task_queue.size <= 0 && concurrent_generators.size <= 0) { if (task_queue.size <= 0 && concurrent_generators.size <= 0) {
return asyncDelay(IDLE_COOLDOWN) return idleEventPromise.then(() => asyncDelay(IDLE_COOLDOWN))
} }
} }
if (task_queue.size < serverCapacity) { if (task_queue.size < serverCapacity) {
eventSource.fireEvent(EVENT_IDLE, {capacity: serverCapacity - task_queue.size}) if (!idleEventPromise?.isPending) {
idleEventPromise = makeQuerablePromise(eventSource.fireEvent(EVENT_IDLE, {capacity: serverCapacity - task_queue.size}))
}
} }
const completedTasks = [] const completedTasks = []
for (let [generator, promise] of concurrent_generators.entries()) { for (let [generator, promise] of concurrent_generators.entries()) {
@ -1178,8 +1183,8 @@
continue continue
} }
const event = {task, generator}; const event = {task, generator};
eventSource.fireEvent(EVENT_TASK_START, event) // optional beforeStart promise to wait on before starting task. const beforeStart = eventSource.fireEvent(EVENT_TASK_START, event) // optional beforeStart promise to wait on before starting task.
const promise = makeQuerablePromise(Promise.resolve(event.beforeStart)) const promise = makeQuerablePromise(beforeStart.then(() => Promise.resolve(event.beforeStart)))
concurrent_generators.set(event.generator, promise) concurrent_generators.set(event.generator, promise)
task_queue.set(task, event.generator) task_queue.set(task, event.generator)
} }
@ -1204,7 +1209,7 @@
} }
const continuePromise = continueTasks().catch(async function(err) { const continuePromise = continueTasks().catch(async function(err) {
console.error(err) console.error(err)
eventSource.fireEvent(EVENT_UNHANDLED_REJECTION, {reason: err}) await eventSource.fireEvent(EVENT_UNHANDLED_REJECTION, {reason: err})
await asyncDelay(RETRY_DELAY_ON_ERROR) await asyncDelay(RETRY_DELAY_ON_ERROR)
}) })
taskPromise = makeQuerablePromise(continuePromise) taskPromise = makeQuerablePromise(continuePromise)

View File

@ -475,7 +475,7 @@ function makeImage() {
initialText.style.display = 'none' initialText.style.display = 'none'
} }
function onIdle() { async function onIdle() {
const serverCapacity = SD.serverCapacity const serverCapacity = SD.serverCapacity
for (const taskEntry of getUncompletedTaskEntries()) { for (const taskEntry of getUncompletedTaskEntries()) {
if (SD.activeTasks.size >= serverCapacity) { if (SD.activeTasks.size >= serverCapacity) {
@ -487,7 +487,7 @@ function onIdle() {
taskStatusLabel.style.display = 'none' taskStatusLabel.style.display = 'none'
continue continue
} }
onTaskStart(task) await onTaskStart(task)
} }
} }
@ -607,10 +607,11 @@ function onTaskErrorHandler(task, reqBody, instance, reason) {
} }
function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) {
if (typeof stepUpdate !== 'object') { if (typeof stepUpdate === 'object') {
return if (stepUpdate.status === 'succeeded') {
} showImages(reqBody, stepUpdate, outputContainer, false)
if (stepUpdate.status !== 'succeeded') { } else {
task.isProcessing = false
const outputMsg = task['outputMsg'] const outputMsg = task['outputMsg']
let msg = '' let msg = ''
if ('detail' in stepUpdate && typeof stepUpdate.detail === 'string' && stepUpdate.detail.length > 0) { if ('detail' in stepUpdate && typeof stepUpdate.detail === 'string' && stepUpdate.detail.length > 0) {
@ -627,9 +628,8 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) {
msg = `Unexpected Read Error:<br/><pre>StepUpdate: ${JSON.stringify(stepUpdate, undefined, 4)}</pre>` msg = `Unexpected Read Error:<br/><pre>StepUpdate: ${JSON.stringify(stepUpdate, undefined, 4)}</pre>`
} }
logError(msg, stepUpdate, outputMsg) logError(msg, stepUpdate, outputMsg)
return false
} }
showImages(reqBody, stepUpdate, outputContainer, false) }
if (task.isProcessing && task.batchesDone < task.batchCount) { if (task.isProcessing && task.batchesDone < task.batchCount) {
task['taskStatusLabel'].innerText = "Pending" task['taskStatusLabel'].innerText = "Pending"
task['taskStatusLabel'].classList.add('waitingTaskLabel') task['taskStatusLabel'].classList.add('waitingTaskLabel')
@ -640,8 +640,6 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) {
return return
} }
setStatus('request', 'done', 'success')
task.isProcessing = false task.isProcessing = false
task['stopTask'].innerHTML = '<i class="fa-solid fa-trash-can"></i> Remove' task['stopTask'].innerHTML = '<i class="fa-solid fa-trash-can"></i> Remove'
task['taskStatusLabel'].style.display = 'none' task['taskStatusLabel'].style.display = 'none'
@ -680,7 +678,7 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) {
} }
function onTaskStart(task) { async function onTaskStart(task) {
if (!task.isProcessing || task.batchesDone >= task.batchCount) { if (!task.isProcessing || task.batchesDone >= task.batchCount) {
return return
} }
@ -718,22 +716,24 @@ function onTaskStart(task) {
task.outputContainer.insertBefore(outputContainer, task.outputContainer.firstChild) task.outputContainer.insertBefore(outputContainer, task.outputContainer.firstChild)
const eventInfo = {reqBody:newTaskReqBody} const eventInfo = {reqBody:newTaskReqBody}
PLUGINS['TASK_CREATE'].forEach((hook) => { const callbacksPromises = PLUGINS['TASK_CREATE'].map((hook) => {
if (typeof hook !== 'function') { if (typeof hook !== 'function') {
console.error('The provided TASK_CREATE hook is not a function. Hook: %o', hook) console.error('The provided TASK_CREATE hook is not a function. Hook: %o', hook)
return return Promise.reject(new Error('hook is not a function.'))
} }
try { try {
hook.call(task, eventInfo) return Promise.resolve(hook.call(task, eventInfo))
} catch (err) { } catch (err) {
console.error(err) console.error(err)
return Promise.reject(err)
} }
}) })
await Promise.allSettled(callbacksPromises)
let instance = eventInfo.instance let instance = eventInfo.instance
if (!instance) { if (!instance) {
const factory = PLUGINS.OUTPUTS_FORMATS.get(eventInfo.reqBody?.output_format || newTaskReqBody.output_format) const factory = PLUGINS.OUTPUTS_FORMATS.get(eventInfo.reqBody?.output_format || newTaskReqBody.output_format)
if (factory) { if (factory) {
instance = factory(eventInfo.reqBody || newTaskReqBody) instance = await Promise.resolve(factory(eventInfo.reqBody || newTaskReqBody))
} }
if (!instance) { if (!instance) {
console.error(`${factory ? "Factory " + String(factory) : 'No factory defined'} for output format ${eventInfo.reqBody?.output_format || newTaskReqBody.output_format}. Instance is ${instance || 'undefined'}. Using default renderer.`) console.error(`${factory ? "Factory " + String(factory) : 'No factory defined'} for output format ${eventInfo.reqBody?.output_format || newTaskReqBody.output_format}. Instance is ${instance || 'undefined'}. Using default renderer.`)
@ -759,8 +759,37 @@ function onTaskStart(task) {
previewTools.style.display = 'block' previewTools.style.display = 'block'
} }
/* Hover effect for the init image in the task list */
function createInitImageHover(taskEntry) {
var $tooltip = $( taskEntry.querySelector('.task-fs-initimage') )
$( taskEntry.querySelector('div.task-initimg > img') ).on('mouseenter', function() {
var img = this,
$img = $(img),
offset = $img.offset();
$tooltip
.css({
'top': offset.top,
'left': offset.left,
'z-index': 99999,
'display': 'block'
})
.append($img.clone().css({width:"", height:""}));
})
$tooltip.on('mouseleave', function() {
$tooltip.empty().addClass('hidden');
});
}
function createTask(task) { function createTask(task) {
let taskConfig = `<b>Seed:</b> ${task.seed}, <b>Sampler:</b> ${task.reqBody.sampler}, <b>Inference Steps:</b> ${task.reqBody.num_inference_steps}, <b>Guidance Scale:</b> ${task.reqBody.guidance_scale}, <b>Model:</b> ${task.reqBody.use_stable_diffusion_model}` let taskConfig = ''
if (task.reqBody.init_image !== undefined) {
let h = 80
let w = task.reqBody.width * h / task.reqBody.height >>0
taskConfig += `<div class="task-initimg" style="float:left;"><img style="width:${w}px;height:${h}px;" src="${task.reqBody.init_image}"><div class="task-fs-initimage"></div></div>`
}
taskConfig += `<b>Seed:</b> ${task.seed}, <b>Sampler:</b> ${task.reqBody.sampler}, <b>Inference Steps:</b> ${task.reqBody.num_inference_steps}, <b>Guidance Scale:</b> ${task.reqBody.guidance_scale}, <b>Model:</b> ${task.reqBody.use_stable_diffusion_model}`
if (task.reqBody.use_vae_model.trim() !== '') { if (task.reqBody.use_vae_model.trim() !== '') {
taskConfig += `, <b>VAE:</b> ${task.reqBody.use_vae_model}` taskConfig += `, <b>VAE:</b> ${task.reqBody.use_vae_model}`
} }
@ -799,6 +828,11 @@ function createTask(task) {
createCollapsibles(taskEntry) createCollapsibles(taskEntry)
if (task.reqBody.init_image !== undefined) {
createInitImageHover(taskEntry)
}
task['taskStatusLabel'] = taskEntry.querySelector('.taskStatusLabel') task['taskStatusLabel'] = taskEntry.querySelector('.taskStatusLabel')
task['outputContainer'] = taskEntry.querySelector('.img-preview') task['outputContainer'] = taskEntry.querySelector('.img-preview')
task['outputMsg'] = taskEntry.querySelector('.outputMsg') task['outputMsg'] = taskEntry.querySelector('.outputMsg')

View File

@ -561,16 +561,22 @@ class GenericEventSource {
throw new Error(`Event ${String(name)} missing from Events.types`) throw new Error(`Event ${String(name)} missing from Events.types`)
} }
if (!this.#events.hasOwnProperty(name)) { if (!this.#events.hasOwnProperty(name)) {
return return Promise.resolve()
} }
if (!args || !args.length) { if (!args || !args.length) {
args = [] args = []
} }
const evs = this.#events[name] const evs = this.#events[name]
const len = evs.length if (evs.length <= 0) {
for (let i = 0; i < len; ++i) { return Promise.resolve()
evs[i].apply(SD, args)
} }
return Promise.allSettled(evs.map((callback) => {
try {
return Promise.resolve(callback.apply(SD, args))
} catch (ex) {
return Promise.reject(ex)
}
}))
} }
} }

View File

@ -17,17 +17,11 @@
prettifyInputs(document); prettifyInputs(document);
let autoScroll = document.querySelector("#auto_scroll") let autoScroll = document.querySelector("#auto_scroll")
/** // save/restore the toggle state
* the use of initSettings() in the autoscroll plugin seems to be breaking the models dropdown and the save-to-disk folder field autoScroll.addEventListener('click', (e) => {
* in the settings tab. They're both blank, because they're being re-initialized. Their earlier values came from the API call, localStorage.setItem('auto_scroll', autoScroll.checked)
* but those values aren't stored in localStorage, since they aren't user-specified. })
* So when initSettings() is called a second time, it overwrites the values with an empty string. autoScroll.checked = localStorage.getItem('auto_scroll') == "true"
*
* We could either rework how new components can register themselves to be auto-saved, without having to call initSettings() again.
* Or we could move the autoscroll code into the main code, and include it in the list of fields in auto-save.js
*/
// SETTINGS_IDS_LIST.push("auto_scroll")
// initSettings()
// observe for changes in the preview pane // observe for changes in the preview pane
var observer = new MutationObserver(function (mutations) { var observer = new MutationObserver(function (mutations) {