diff --git a/.gitignore b/.gitignore index b5157e17..90bf0a44 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ installer installer.tar dist .idea/* +node_modules/* \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..1f28e901 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +*.min.* +*.py +/* +!/ui +/ui/easydiffusion +/ui/hotfix +!/ui/plugins +!/ui/media \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..d8ec3571 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "printWidth": 120, + "tabWidth": 4, + "semi": false, + "arrowParens": "always", + "trailingComma": "none" +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..c9c03893 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "scripts": { + "prettier": "prettier --write \"./**/*.js\"" + }, + "devDependencies": { + "prettier": "^1.19.1" + } +} diff --git a/ui/media/js/auto-save.js b/ui/media/js/auto-save.js index 32d9ad1e..0bf9d855 100644 --- a/ui/media/js/auto-save.js +++ b/ui/media/js/auto-save.js @@ -55,24 +55,24 @@ const SETTINGS_IDS_LIST = [ "json_toggle" ] -const IGNORE_BY_DEFAULT = [ - "prompt" -] +const IGNORE_BY_DEFAULT = ["prompt"] -const SETTINGS_SECTIONS = [ // gets the "keys" property filled in with an ordered list of settings in this section via initSettings - { id: "editor-inputs", name: "Prompt" }, +const SETTINGS_SECTIONS = [ + // gets the "keys" property filled in with an ordered list of settings in this section via initSettings + { id: "editor-inputs", name: "Prompt" }, { id: "editor-settings", name: "Image Settings" }, { id: "system-settings", name: "System Settings" }, - { id: "container", name: "Other" } + { id: "container", name: "Other" } ] async function initSettings() { - SETTINGS_IDS_LIST.forEach(id => { + SETTINGS_IDS_LIST.forEach((id) => { var element = document.getElementById(id) if (!element) { console.error(`Missing settings element ${id}`) } - if (id in SETTINGS) { // don't create it again + if (id in SETTINGS) { + // don't create it again return } SETTINGS[id] = { @@ -87,22 +87,22 @@ async function initSettings() { element.addEventListener("change", settingChangeHandler) }) var unsorted_settings_ids = [...SETTINGS_IDS_LIST] - SETTINGS_SECTIONS.forEach(section => { + SETTINGS_SECTIONS.forEach((section) => { var name = section.name var element = document.getElementById(section.id) - var unsorted_ids = unsorted_settings_ids.map(id => `#${id}`).join(",") - var children = unsorted_ids == "" ? [] : Array.from(element.querySelectorAll(unsorted_ids)); + var unsorted_ids = unsorted_settings_ids.map((id) => `#${id}`).join(",") + var children = unsorted_ids == "" ? [] : Array.from(element.querySelectorAll(unsorted_ids)) section.keys = [] - children.forEach(e => { + children.forEach((e) => { section.keys.push(e.id) }) - unsorted_settings_ids = unsorted_settings_ids.filter(id => children.find(e => e.id == id) == undefined) + unsorted_settings_ids = unsorted_settings_ids.filter((id) => children.find((e) => e.id == id) == undefined) }) loadSettings() } function getSetting(element) { - if (element.dataset && 'path' in element.dataset) { + if (element.dataset && "path" in element.dataset) { return element.dataset.path } if (typeof element === "string" || element instanceof String) { @@ -114,7 +114,7 @@ function getSetting(element) { return element.value } function setSetting(element, value) { - if (element.dataset && 'path' in element.dataset) { + if (element.dataset && "path" in element.dataset) { element.dataset.path = value return // no need to dispatch any event here because the models are not loaded yet } @@ -127,8 +127,7 @@ function setSetting(element, value) { } if (element.type == "checkbox") { element.checked = value - } - else { + } else { element.value = value } element.dispatchEvent(new Event("input")) @@ -136,7 +135,7 @@ function setSetting(element, value) { } function saveSettings() { - var saved_settings = Object.values(SETTINGS).map(setting => { + var saved_settings = Object.values(SETTINGS).map((setting) => { return { key: setting.key, value: setting.value, @@ -151,16 +150,16 @@ function loadSettings() { var saved_settings_text = localStorage.getItem(SETTINGS_KEY) if (saved_settings_text) { var saved_settings = JSON.parse(saved_settings_text) - if (saved_settings.find(s => s.key == "auto_save_settings")?.value == false) { + if (saved_settings.find((s) => s.key == "auto_save_settings")?.value == false) { setSetting("auto_save_settings", false) return } CURRENTLY_LOADING_SETTINGS = true - saved_settings.forEach(saved_setting => { + saved_settings.forEach((saved_setting) => { var setting = SETTINGS[saved_setting.key] if (!setting) { - console.warn(`Attempted to load setting ${saved_setting.key}, but no setting found`); - return null; + console.warn(`Attempted to load setting ${saved_setting.key}, but no setting found`) + return null } setting.ignore = saved_setting.ignore if (!setting.ignore) { @@ -169,10 +168,9 @@ function loadSettings() { } }) CURRENTLY_LOADING_SETTINGS = false - } - else { + } else { CURRENTLY_LOADING_SETTINGS = true - tryLoadOldSettings(); + tryLoadOldSettings() CURRENTLY_LOADING_SETTINGS = false saveSettings() } @@ -180,9 +178,9 @@ function loadSettings() { function loadDefaultSettingsSection(section_id) { CURRENTLY_LOADING_SETTINGS = true - var section = SETTINGS_SECTIONS.find(s => s.id == section_id); - section.keys.forEach(key => { - var setting = SETTINGS[key]; + var section = SETTINGS_SECTIONS.find((s) => s.id == section_id) + section.keys.forEach((key) => { + var setting = SETTINGS[key] setting.value = setting.default setSetting(setting.element, setting.value) }) @@ -218,10 +216,10 @@ function getSettingLabel(element) { function fillSaveSettingsConfigTable() { saveSettingsConfigTable.textContent = "" - SETTINGS_SECTIONS.forEach(section => { + SETTINGS_SECTIONS.forEach((section) => { var section_row = `${section.name}` saveSettingsConfigTable.insertAdjacentHTML("beforeend", section_row) - section.keys.forEach(key => { + section.keys.forEach((key) => { var setting = SETTINGS[key] var element = setting.element var checkbox_id = `shouldsave_${element.id}` @@ -234,7 +232,7 @@ function fillSaveSettingsConfigTable() { var newrow = `(${value})` saveSettingsConfigTable.insertAdjacentHTML("beforeend", newrow) var checkbox = document.getElementById(checkbox_id) - checkbox.addEventListener("input", event => { + checkbox.addEventListener("input", (event) => { setting.ignore = !checkbox.checked saveSettings() }) @@ -245,9 +243,6 @@ function fillSaveSettingsConfigTable() { // configureSettingsSaveBtn - - - var autoSaveSettings = document.getElementById("auto_save_settings") var configSettingsButton = document.createElement("button") configSettingsButton.textContent = "Configure" @@ -256,33 +251,32 @@ autoSaveSettings.insertAdjacentElement("beforebegin", configSettingsButton) autoSaveSettings.addEventListener("change", () => { configSettingsButton.style.display = autoSaveSettings.checked ? "block" : "none" }) -configSettingsButton.addEventListener('click', () => { +configSettingsButton.addEventListener("click", () => { fillSaveSettingsConfigTable() saveSettingsConfigOverlay.classList.add("active") }) -resetImageSettingsButton.addEventListener('click', event => { - loadDefaultSettingsSection("editor-settings"); +resetImageSettingsButton.addEventListener("click", (event) => { + loadDefaultSettingsSection("editor-settings") event.stopPropagation() }) - function tryLoadOldSettings() { console.log("Loading old user settings") // load v1 auto-save.js settings var old_map = { - "guidance_scale_slider": "guidance_scale", - "prompt_strength_slider": "prompt_strength" + guidance_scale_slider: "guidance_scale", + prompt_strength_slider: "prompt_strength" } var settings_key_v1 = "user_settings" var saved_settings_text = localStorage.getItem(settings_key_v1) if (saved_settings_text) { var saved_settings = JSON.parse(saved_settings_text) - Object.keys(saved_settings.should_save).forEach(key => { + Object.keys(saved_settings.should_save).forEach((key) => { key = key in old_map ? old_map[key] : key if (!(key in SETTINGS)) return SETTINGS[key].ignore = !saved_settings.should_save[key] - }); - Object.keys(saved_settings.values).forEach(key => { + }) + Object.keys(saved_settings.values).forEach((key) => { key = key in old_map ? old_map[key] : key if (!(key in SETTINGS)) return var setting = SETTINGS[key] @@ -290,38 +284,42 @@ function tryLoadOldSettings() { setting.value = saved_settings.values[key] setSetting(setting.element, setting.value) } - }); + }) localStorage.removeItem(settings_key_v1) } // load old individually stored items - var individual_settings_map = { // maps old localStorage-key to new SETTINGS-key - "soundEnabled": "sound_toggle", - "saveToDisk": "save_to_disk", - "useCPU": "use_cpu", - "diskPath": "diskPath", - "useFaceCorrection": "use_face_correction", - "useUpscaling": "use_upscale", - "showOnlyFilteredImage": "show_only_filtered_image", - "streamImageProgress": "stream_image_progress", - "outputFormat": "output_format", - "autoSaveSettings": "auto_save_settings", - }; - Object.keys(individual_settings_map).forEach(localStorageKey => { - var localStorageValue = localStorage.getItem(localStorageKey); + var individual_settings_map = { + // maps old localStorage-key to new SETTINGS-key + soundEnabled: "sound_toggle", + saveToDisk: "save_to_disk", + useCPU: "use_cpu", + diskPath: "diskPath", + useFaceCorrection: "use_face_correction", + useUpscaling: "use_upscale", + showOnlyFilteredImage: "show_only_filtered_image", + streamImageProgress: "stream_image_progress", + outputFormat: "output_format", + autoSaveSettings: "auto_save_settings" + } + Object.keys(individual_settings_map).forEach((localStorageKey) => { + var localStorageValue = localStorage.getItem(localStorageKey) if (localStorageValue !== null) { let key = individual_settings_map[localStorageKey] var setting = SETTINGS[key] if (!setting) { - console.warn(`Attempted to map old setting ${key}, but no setting found`); - return null; + console.warn(`Attempted to map old setting ${key}, but no setting found`) + return null } - if (setting.element.type == "checkbox" && (typeof localStorageValue === "string" || localStorageValue instanceof String)) { + if ( + setting.element.type == "checkbox" && + (typeof localStorageValue === "string" || localStorageValue instanceof String) + ) { localStorageValue = localStorageValue == "true" } setting.value = localStorageValue setSetting(setting.element, setting.value) - localStorage.removeItem(localStorageKey); + localStorage.removeItem(localStorageKey) } }) } diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index ebcce132..aa487c94 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -1,25 +1,25 @@ "use strict" // Opt in to a restricted variant of JavaScript const EXT_REGEX = /(?:\.([^.]+))?$/ -const TEXT_EXTENSIONS = ['txt', 'json'] -const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'tga', 'webp'] +const TEXT_EXTENSIONS = ["txt", "json"] +const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "bmp", "tiff", "tif", "tga", "webp"] function parseBoolean(stringValue) { - if (typeof stringValue === 'boolean') { + if (typeof stringValue === "boolean") { return stringValue } - if (typeof stringValue === 'number') { + if (typeof stringValue === "number") { return stringValue !== 0 } - if (typeof stringValue !== 'string') { + if (typeof stringValue !== "string") { return false } - switch(stringValue?.toLowerCase()?.trim()) { + switch (stringValue?.toLowerCase()?.trim()) { case "true": case "yes": case "on": case "1": - return true; + return true case "false": case "no": @@ -28,45 +28,50 @@ function parseBoolean(stringValue) { case "none": case null: case undefined: - return false; + return false } try { - return Boolean(JSON.parse(stringValue)); + return Boolean(JSON.parse(stringValue)) } catch { return Boolean(stringValue) } } const TASK_MAPPING = { - prompt: { name: 'Prompt', + prompt: { + name: "Prompt", setUI: (prompt) => { promptField.value = prompt }, readUI: () => promptField.value, parse: (val) => val }, - negative_prompt: { name: 'Negative Prompt', + negative_prompt: { + name: "Negative Prompt", setUI: (negative_prompt) => { negativePromptField.value = negative_prompt }, readUI: () => negativePromptField.value, parse: (val) => val }, - active_tags: { name: "Image Modifiers", + active_tags: { + name: "Image Modifiers", setUI: (active_tags) => { refreshModifiersState(active_tags) }, - readUI: () => activeTags.map(x => x.name), + readUI: () => activeTags.map((x) => x.name), parse: (val) => val }, - inactive_tags: { name: "Inactive Image Modifiers", + inactive_tags: { + name: "Inactive Image Modifiers", setUI: (inactive_tags) => { refreshInactiveTags(inactive_tags) }, - readUI: () => activeTags.filter(tag => tag.inactive === true).map(x => x.name), + readUI: () => activeTags.filter((tag) => tag.inactive === true).map((x) => x.name), parse: (val) => val }, - width: { name: 'Width', + width: { + name: "Width", setUI: (width) => { const oldVal = widthField.value widthField.value = width @@ -77,7 +82,8 @@ const TASK_MAPPING = { readUI: () => parseInt(widthField.value), parse: (val) => parseInt(val) }, - height: { name: 'Height', + height: { + name: "Height", setUI: (height) => { const oldVal = heightField.value heightField.value = height @@ -88,7 +94,8 @@ const TASK_MAPPING = { readUI: () => parseInt(heightField.value), parse: (val) => parseInt(val) }, - seed: { name: 'Seed', + seed: { + name: "Seed", setUI: (seed) => { if (!seed) { randomSeedField.checked = true @@ -97,21 +104,23 @@ const TASK_MAPPING = { return } randomSeedField.checked = false - randomSeedField.dispatchEvent(new Event('change')) // let plugins know that the state of the random seed toggle changed + randomSeedField.dispatchEvent(new Event("change")) // let plugins know that the state of the random seed toggle changed seedField.disabled = false seedField.value = seed }, readUI: () => parseInt(seedField.value), // just return the value the user is seeing in the UI parse: (val) => parseInt(val) }, - num_inference_steps: { name: 'Steps', + num_inference_steps: { + name: "Steps", setUI: (num_inference_steps) => { numInferenceStepsField.value = num_inference_steps }, readUI: () => parseInt(numInferenceStepsField.value), parse: (val) => parseInt(val) }, - guidance_scale: { name: 'Guidance Scale', + guidance_scale: { + name: "Guidance Scale", setUI: (guidance_scale) => { guidanceScaleField.value = guidance_scale updateGuidanceScaleSlider() @@ -119,7 +128,8 @@ const TASK_MAPPING = { readUI: () => parseFloat(guidanceScaleField.value), parse: (val) => parseFloat(val) }, - prompt_strength: { name: 'Prompt Strength', + prompt_strength: { + name: "Prompt Strength", setUI: (prompt_strength) => { promptStrengthField.value = prompt_strength updatePromptStrengthSlider() @@ -128,16 +138,19 @@ const TASK_MAPPING = { parse: (val) => parseFloat(val) }, - init_image: { name: 'Initial Image', + init_image: { + name: "Initial Image", setUI: (init_image) => { initImagePreview.src = init_image }, readUI: () => initImagePreview.src, parse: (val) => val }, - mask: { name: 'Mask', + mask: { + name: "Mask", setUI: (mask) => { - setTimeout(() => { // add a delay to insure this happens AFTER the main image loads (which reloads the inpainter) + setTimeout(() => { + // add a delay to insure this happens AFTER the main image loads (which reloads the inpainter) imageInpainter.setImg(mask) }, 250) maskSetting.checked = Boolean(mask) @@ -145,22 +158,26 @@ const TASK_MAPPING = { readUI: () => (maskSetting.checked ? imageInpainter.getImg() : undefined), parse: (val) => val }, - preserve_init_image_color_profile: { name: 'Preserve Color Profile', + preserve_init_image_color_profile: { + name: "Preserve Color Profile", setUI: (preserve_init_image_color_profile) => { applyColorCorrectionField.checked = parseBoolean(preserve_init_image_color_profile) }, readUI: () => applyColorCorrectionField.checked, parse: (val) => parseBoolean(val) }, - - use_face_correction: { name: 'Use Face Correction', + + use_face_correction: { + name: "Use Face Correction", setUI: (use_face_correction) => { const oldVal = gfpganModelField.value - gfpganModelField.value = getModelPath(use_face_correction, ['.pth']) - if (gfpganModelField.value) { // Is a valid value for the field. + gfpganModelField.value = getModelPath(use_face_correction, [".pth"]) + if (gfpganModelField.value) { + // Is a valid value for the field. useFaceCorrectionField.checked = true gfpganModelField.disabled = false - } else { // Not a valid value, restore the old value and disable the filter. + } else { + // Not a valid value, restore the old value and disable the filter. gfpganModelField.disabled = true gfpganModelField.value = oldVal useFaceCorrectionField.checked = false @@ -171,15 +188,18 @@ const TASK_MAPPING = { readUI: () => (useFaceCorrectionField.checked ? gfpganModelField.value : undefined), parse: (val) => val }, - use_upscale: { name: 'Use Upscaling', + use_upscale: { + name: "Use Upscaling", setUI: (use_upscale) => { const oldVal = upscaleModelField.value - upscaleModelField.value = getModelPath(use_upscale, ['.pth']) - if (upscaleModelField.value) { // Is a valid value for the field. + upscaleModelField.value = getModelPath(use_upscale, [".pth"]) + if (upscaleModelField.value) { + // Is a valid value for the field. useUpscalingField.checked = true upscaleModelField.disabled = false upscaleAmountField.disabled = false - } else { // Not a valid value, restore the old value and disable the filter. + } else { + // Not a valid value, restore the old value and disable the filter. upscaleModelField.disabled = true upscaleAmountField.disabled = true upscaleModelField.value = oldVal @@ -189,25 +209,28 @@ const TASK_MAPPING = { readUI: () => (useUpscalingField.checked ? upscaleModelField.value : undefined), parse: (val) => val }, - upscale_amount: { name: 'Upscale By', + upscale_amount: { + name: "Upscale By", setUI: (upscale_amount) => { upscaleAmountField.value = upscale_amount }, readUI: () => upscaleAmountField.value, parse: (val) => val }, - sampler_name: { name: 'Sampler', + sampler_name: { + name: "Sampler", setUI: (sampler_name) => { samplerField.value = sampler_name }, readUI: () => samplerField.value, parse: (val) => val }, - use_stable_diffusion_model: { name: 'Stable Diffusion model', + use_stable_diffusion_model: { + name: "Stable Diffusion model", setUI: (use_stable_diffusion_model) => { const oldVal = stableDiffusionModelField.value - use_stable_diffusion_model = getModelPath(use_stable_diffusion_model, ['.ckpt', '.safetensors']) + use_stable_diffusion_model = getModelPath(use_stable_diffusion_model, [".ckpt", ".safetensors"]) stableDiffusionModelField.value = use_stable_diffusion_model if (!stableDiffusionModelField.value) { @@ -217,35 +240,42 @@ const TASK_MAPPING = { readUI: () => stableDiffusionModelField.value, parse: (val) => val }, - use_vae_model: { name: 'VAE model', + use_vae_model: { + name: "VAE model", setUI: (use_vae_model) => { const oldVal = vaeModelField.value - use_vae_model = (use_vae_model === undefined || use_vae_model === null || use_vae_model === 'None' ? '' : use_vae_model) + use_vae_model = + use_vae_model === undefined || use_vae_model === null || use_vae_model === "None" ? "" : use_vae_model - if (use_vae_model !== '') { - use_vae_model = getModelPath(use_vae_model, ['.vae.pt', '.ckpt']) - use_vae_model = use_vae_model !== '' ? use_vae_model : oldVal + if (use_vae_model !== "") { + use_vae_model = getModelPath(use_vae_model, [".vae.pt", ".ckpt"]) + use_vae_model = use_vae_model !== "" ? use_vae_model : oldVal } vaeModelField.value = use_vae_model }, readUI: () => vaeModelField.value, parse: (val) => val }, - use_lora_model: { name: 'LoRA model', + use_lora_model: { + name: "LoRA model", setUI: (use_lora_model) => { const oldVal = loraModelField.value - use_lora_model = (use_lora_model === undefined || use_lora_model === null || use_lora_model === 'None' ? '' : use_lora_model) + use_lora_model = + use_lora_model === undefined || use_lora_model === null || use_lora_model === "None" + ? "" + : use_lora_model - if (use_lora_model !== '') { - use_lora_model = getModelPath(use_lora_model, ['.ckpt', '.safetensors']) - use_lora_model = use_lora_model !== '' ? use_lora_model : oldVal + if (use_lora_model !== "") { + use_lora_model = getModelPath(use_lora_model, [".ckpt", ".safetensors"]) + use_lora_model = use_lora_model !== "" ? use_lora_model : oldVal } loraModelField.value = use_lora_model }, readUI: () => loraModelField.value, parse: (val) => val }, - lora_alpha: { name: 'LoRA Strength', + lora_alpha: { + name: "LoRA Strength", setUI: (lora_alpha) => { loraAlphaField.value = lora_alpha updateLoraAlphaSlider() @@ -253,22 +283,29 @@ const TASK_MAPPING = { readUI: () => parseFloat(loraAlphaField.value), parse: (val) => parseFloat(val) }, - use_hypernetwork_model: { name: 'Hypernetwork model', + use_hypernetwork_model: { + name: "Hypernetwork model", setUI: (use_hypernetwork_model) => { const oldVal = hypernetworkModelField.value - use_hypernetwork_model = (use_hypernetwork_model === undefined || use_hypernetwork_model === null || use_hypernetwork_model === 'None' ? '' : use_hypernetwork_model) + use_hypernetwork_model = + use_hypernetwork_model === undefined || + use_hypernetwork_model === null || + use_hypernetwork_model === "None" + ? "" + : use_hypernetwork_model - if (use_hypernetwork_model !== '') { - use_hypernetwork_model = getModelPath(use_hypernetwork_model, ['.pt']) - use_hypernetwork_model = use_hypernetwork_model !== '' ? use_hypernetwork_model : oldVal + if (use_hypernetwork_model !== "") { + use_hypernetwork_model = getModelPath(use_hypernetwork_model, [".pt"]) + use_hypernetwork_model = use_hypernetwork_model !== "" ? use_hypernetwork_model : oldVal } hypernetworkModelField.value = use_hypernetwork_model - hypernetworkModelField.dispatchEvent(new Event('change')) + hypernetworkModelField.dispatchEvent(new Event("change")) }, readUI: () => hypernetworkModelField.value, parse: (val) => val }, - hypernetwork_strength: { name: 'Hypernetwork Strength', + hypernetwork_strength: { + name: "Hypernetwork Strength", setUI: (hypernetwork_strength) => { hypernetworkStrengthField.value = hypernetwork_strength updateHypernetworkStrengthSlider() @@ -277,7 +314,8 @@ const TASK_MAPPING = { parse: (val) => parseFloat(val) }, - num_outputs: { name: 'Parallel Images', + num_outputs: { + name: "Parallel Images", setUI: (num_outputs) => { numOutputsParallelField.value = num_outputs }, @@ -285,7 +323,8 @@ const TASK_MAPPING = { parse: (val) => val }, - use_cpu: { name: 'Use CPU', + use_cpu: { + name: "Use CPU", setUI: (use_cpu) => { useCPUField.checked = use_cpu }, @@ -293,28 +332,32 @@ const TASK_MAPPING = { parse: (val) => val }, - stream_image_progress: { name: 'Stream Image Progress', + stream_image_progress: { + name: "Stream Image Progress", setUI: (stream_image_progress) => { - streamImageProgressField.checked = (parseInt(numOutputsTotalField.value) > 50 ? false : stream_image_progress) + streamImageProgressField.checked = parseInt(numOutputsTotalField.value) > 50 ? false : stream_image_progress }, readUI: () => streamImageProgressField.checked, parse: (val) => Boolean(val) }, - show_only_filtered_image: { name: 'Show only the corrected/upscaled image', + show_only_filtered_image: { + name: "Show only the corrected/upscaled image", setUI: (show_only_filtered_image) => { showOnlyFilteredImageField.checked = show_only_filtered_image }, readUI: () => showOnlyFilteredImageField.checked, parse: (val) => Boolean(val) }, - output_format: { name: 'Output Format', + output_format: { + name: "Output Format", setUI: (output_format) => { outputFormatField.value = output_format }, readUI: () => outputFormatField.value, parse: (val) => val }, - save_to_disk_path: { name: 'Save to disk path', + save_to_disk_path: { + name: "Save to disk path", setUI: (save_to_disk_path) => { saveToDiskField.checked = Boolean(save_to_disk_path) diskPathField.value = save_to_disk_path @@ -327,14 +370,14 @@ const TASK_MAPPING = { function restoreTaskToUI(task, fieldsToSkip) { fieldsToSkip = fieldsToSkip || [] - if ('numOutputsTotal' in task) { + if ("numOutputsTotal" in task) { numOutputsTotalField.value = task.numOutputsTotal } - if ('seed' in task) { + if ("seed" in task) { randomSeedField.checked = false seedField.value = task.seed } - if (!('reqBody' in task)) { + if (!("reqBody" in task)) { return } for (const key in TASK_MAPPING) { @@ -344,31 +387,31 @@ function restoreTaskToUI(task, fieldsToSkip) { } // properly reset fields not present in the task - if (!('use_hypernetwork_model' in task.reqBody)) { + if (!("use_hypernetwork_model" in task.reqBody)) { hypernetworkModelField.value = "" hypernetworkModelField.dispatchEvent(new Event("change")) } - - if (!('use_lora_model' in task.reqBody)) { + + if (!("use_lora_model" in task.reqBody)) { loraModelField.value = "" loraModelField.dispatchEvent(new Event("change")) } - + // restore the original prompt if provided (e.g. use settings), fallback to prompt as needed (e.g. copy/paste or d&d) promptField.value = task.reqBody.original_prompt - if (!('original_prompt' in task.reqBody)) { + if (!("original_prompt" in task.reqBody)) { promptField.value = task.reqBody.prompt } - + // properly reset checkboxes - if (!('use_face_correction' in task.reqBody)) { + if (!("use_face_correction" in task.reqBody)) { useFaceCorrectionField.checked = false gfpganModelField.disabled = true } - if (!('use_upscale' in task.reqBody)) { + if (!("use_upscale" in task.reqBody)) { useUpscalingField.checked = false } - if (!('mask' in task.reqBody) && maskSetting.checked) { + if (!("mask" in task.reqBody) && maskSetting.checked) { maskSetting.checked = false maskSetting.dispatchEvent(new Event("click")) } @@ -379,15 +422,18 @@ function restoreTaskToUI(task, fieldsToSkip) { if (IMAGE_REGEX.test(initImagePreview.src) && task.reqBody.init_image == undefined) { // hide source image initImageClearBtn.dispatchEvent(new Event("click")) - } - else if (task.reqBody.init_image !== undefined) { + } else if (task.reqBody.init_image !== undefined) { // listen for inpainter loading event, which happens AFTER the main image loads (which reloads the inpainter) - initImagePreview.addEventListener('load', function() { - if (Boolean(task.reqBody.mask)) { - imageInpainter.setImg(task.reqBody.mask) - maskSetting.checked = true - } - }, { once: true }) + initImagePreview.addEventListener( + "load", + function() { + if (Boolean(task.reqBody.mask)) { + imageInpainter.setImg(task.reqBody.mask) + maskSetting.checked = true + } + }, + { once: true } + ) initImagePreview.src = task.reqBody.init_image } } @@ -397,28 +443,26 @@ function readUI() { reqBody[key] = TASK_MAPPING[key].readUI() } return { - 'numOutputsTotal': parseInt(numOutputsTotalField.value), - 'seed': TASK_MAPPING['seed'].readUI(), - 'reqBody': reqBody + numOutputsTotal: parseInt(numOutputsTotalField.value), + seed: TASK_MAPPING["seed"].readUI(), + reqBody: reqBody } } -function getModelPath(filename, extensions) -{ +function getModelPath(filename, extensions) { if (typeof filename !== "string") { return } - + let pathIdx - if (filename.includes('/models/stable-diffusion/')) { - pathIdx = filename.indexOf('/models/stable-diffusion/') + 25 // Linux, Mac paths - } - else if (filename.includes('\\models\\stable-diffusion\\')) { - pathIdx = filename.indexOf('\\models\\stable-diffusion\\') + 25 // Linux, Mac paths + if (filename.includes("/models/stable-diffusion/")) { + pathIdx = filename.indexOf("/models/stable-diffusion/") + 25 // Linux, Mac paths + } else if (filename.includes("\\models\\stable-diffusion\\")) { + pathIdx = filename.indexOf("\\models\\stable-diffusion\\") + 25 // Linux, Mac paths } if (pathIdx >= 0) { filename = filename.slice(pathIdx) } - extensions.forEach(ext => { + extensions.forEach((ext) => { if (filename.endsWith(ext)) { filename = filename.slice(0, filename.length - ext.length) } @@ -427,26 +471,26 @@ function getModelPath(filename, extensions) } const TASK_TEXT_MAPPING = { - prompt: 'Prompt', - width: 'Width', - height: 'Height', - seed: 'Seed', - num_inference_steps: 'Steps', - guidance_scale: 'Guidance Scale', - prompt_strength: 'Prompt Strength', - use_face_correction: 'Use Face Correction', - use_upscale: 'Use Upscaling', - upscale_amount: 'Upscale By', - sampler_name: 'Sampler', - negative_prompt: 'Negative Prompt', - use_stable_diffusion_model: 'Stable Diffusion model', - use_hypernetwork_model: 'Hypernetwork model', - hypernetwork_strength: 'Hypernetwork Strength' + prompt: "Prompt", + width: "Width", + height: "Height", + seed: "Seed", + num_inference_steps: "Steps", + guidance_scale: "Guidance Scale", + prompt_strength: "Prompt Strength", + use_face_correction: "Use Face Correction", + use_upscale: "Use Upscaling", + upscale_amount: "Upscale By", + sampler_name: "Sampler", + negative_prompt: "Negative Prompt", + use_stable_diffusion_model: "Stable Diffusion model", + use_hypernetwork_model: "Hypernetwork model", + hypernetwork_strength: "Hypernetwork Strength" } function parseTaskFromText(str) { const taskReqBody = {} - const lines = str.split('\n') + const lines = str.split("\n") if (lines.length === 0) { return } @@ -454,14 +498,14 @@ function parseTaskFromText(str) { // Prompt let knownKeyOnFirstLine = false for (let key in TASK_TEXT_MAPPING) { - if (lines[0].startsWith(TASK_TEXT_MAPPING[key] + ':')) { + if (lines[0].startsWith(TASK_TEXT_MAPPING[key] + ":")) { knownKeyOnFirstLine = true break } } if (!knownKeyOnFirstLine) { taskReqBody.prompt = lines[0] - console.log('Prompt:', taskReqBody.prompt) + console.log("Prompt:", taskReqBody.prompt) } for (const key in TASK_TEXT_MAPPING) { @@ -469,18 +513,18 @@ function parseTaskFromText(str) { continue } - const name = TASK_TEXT_MAPPING[key]; + const name = TASK_TEXT_MAPPING[key] let val = undefined - const reName = new RegExp(`${name}\\ *:\\ *(.*)(?:\\r\\n|\\r|\\n)*`, 'igm') - const match = reName.exec(str); + const reName = new RegExp(`${name}\\ *:\\ *(.*)(?:\\r\\n|\\r|\\n)*`, "igm") + const match = reName.exec(str) if (match) { str = str.slice(0, match.index) + str.slice(match.index + match[0].length) val = match[1] } if (val !== undefined) { taskReqBody[key] = TASK_MAPPING[key].parse(val.trim()) - console.log(TASK_MAPPING[key].name + ':', taskReqBody[key]) + console.log(TASK_MAPPING[key].name + ":", taskReqBody[key]) if (!str) { break } @@ -490,18 +534,19 @@ function parseTaskFromText(str) { return undefined } const task = { reqBody: taskReqBody } - if ('seed' in taskReqBody) { + if ("seed" in taskReqBody) { task.seed = taskReqBody.seed } return task } async function parseContent(text) { - text = text.trim(); - if (text.startsWith('{') && text.endsWith('}')) { + text = text.trim() + if (text.startsWith("{") && text.endsWith("}")) { try { const task = JSON.parse(text) - if (!('reqBody' in task)) { // support the format saved to the disk, by the UI + if (!("reqBody" in task)) { + // support the format saved to the disk, by the UI task.reqBody = Object.assign({}, task) } restoreTaskToUI(task) @@ -513,7 +558,8 @@ async function parseContent(text) { } // Normal txt file. const task = parseTaskFromText(text) - if (text.toLowerCase().includes('seed:') && task) { // only parse valid task content + if (text.toLowerCase().includes("seed:") && task) { + // only parse valid task content restoreTaskToUI(task) return true } else { @@ -530,21 +576,25 @@ async function readFile(file, i) { } function dropHandler(ev) { - console.log('Content dropped...') + console.log("Content dropped...") let items = [] - if (ev?.dataTransfer?.items) { // Use DataTransferItemList interface + if (ev?.dataTransfer?.items) { + // Use DataTransferItemList interface items = Array.from(ev.dataTransfer.items) - items = items.filter(item => item.kind === 'file') - items = items.map(item => item.getAsFile()) - } else if (ev?.dataTransfer?.files) { // Use DataTransfer interface + items = items.filter((item) => item.kind === "file") + items = items.map((item) => item.getAsFile()) + } else if (ev?.dataTransfer?.files) { + // Use DataTransfer interface items = Array.from(ev.dataTransfer.files) } - items.forEach(item => {item.file_ext = EXT_REGEX.exec(item.name.toLowerCase())[1]}) + items.forEach((item) => { + item.file_ext = EXT_REGEX.exec(item.name.toLowerCase())[1] + }) - let text_items = items.filter(item => TEXT_EXTENSIONS.includes(item.file_ext)) - let image_items = items.filter(item => IMAGE_EXTENSIONS.includes(item.file_ext)) + let text_items = items.filter((item) => TEXT_EXTENSIONS.includes(item.file_ext)) + let image_items = items.filter((item) => IMAGE_EXTENSIONS.includes(item.file_ext)) if (image_items.length > 0 && ev.target == initImageSelector) { return // let the event bubble up, so that the Init Image filepicker can receive this @@ -554,7 +604,7 @@ function dropHandler(ev) { text_items.forEach(readFile) } function dragOverHandler(ev) { - console.log('Content in drop zone') + console.log("Content in drop zone") // Prevent default behavior (Prevent file/content from being opened) ev.preventDefault() @@ -562,73 +612,72 @@ function dragOverHandler(ev) { ev.dataTransfer.dropEffect = "copy" let img = new Image() - img.src = '//' + location.host + '/media/images/favicon-32x32.png' + img.src = "//" + location.host + "/media/images/favicon-32x32.png" ev.dataTransfer.setDragImage(img, 16, 16) } document.addEventListener("drop", dropHandler) document.addEventListener("dragover", dragOverHandler) -const TASK_REQ_NO_EXPORT = [ - "use_cpu", - "save_to_disk_path" -] -const resetSettings = document.getElementById('reset-image-settings') +const TASK_REQ_NO_EXPORT = ["use_cpu", "save_to_disk_path"] +const resetSettings = document.getElementById("reset-image-settings") -function checkReadTextClipboardPermission (result) { +function checkReadTextClipboardPermission(result) { if (result.state != "granted" && result.state != "prompt") { return } // PASTE ICON - const pasteIcon = document.createElement('i') - pasteIcon.className = 'fa-solid fa-paste section-button' + const pasteIcon = document.createElement("i") + pasteIcon.className = "fa-solid fa-paste section-button" pasteIcon.innerHTML = `Paste Image Settings` - pasteIcon.addEventListener('click', async (event) => { + pasteIcon.addEventListener("click", async (event) => { event.stopPropagation() // Add css class 'active' - pasteIcon.classList.add('active') + pasteIcon.classList.add("active") // In 350 ms remove the 'active' class - asyncDelay(350).then(() => pasteIcon.classList.remove('active')) + asyncDelay(350).then(() => pasteIcon.classList.remove("active")) // Retrieve clipboard content and try to parse it - const text = await navigator.clipboard.readText(); + const text = await navigator.clipboard.readText() await parseContent(text) }) resetSettings.parentNode.insertBefore(pasteIcon, resetSettings) } -navigator.permissions.query({ name: "clipboard-read" }).then(checkReadTextClipboardPermission, (reason) => console.log('clipboard-read is not available. %o', reason)) +navigator.permissions + .query({ name: "clipboard-read" }) + .then(checkReadTextClipboardPermission, (reason) => console.log("clipboard-read is not available. %o", reason)) -document.addEventListener('paste', async (event) => { +document.addEventListener("paste", async (event) => { if (event.target) { const targetTag = event.target.tagName.toLowerCase() // Disable when targeting input elements. - if (targetTag === 'input' || targetTag === 'textarea') { + if (targetTag === "input" || targetTag === "textarea") { return } } - const paste = (event.clipboardData || window.clipboardData).getData('text') + const paste = (event.clipboardData || window.clipboardData).getData("text") const selection = window.getSelection() - if (paste != "" && selection.toString().trim().length <= 0 && await parseContent(paste)) { + if (paste != "" && selection.toString().trim().length <= 0 && (await parseContent(paste))) { event.preventDefault() return } }) // Adds a copy and a paste icon if the browser grants permission to write to clipboard. -function checkWriteToClipboardPermission (result) { +function checkWriteToClipboardPermission(result) { if (result.state != "granted" && result.state != "prompt") { return } // COPY ICON - const copyIcon = document.createElement('i') - copyIcon.className = 'fa-solid fa-clipboard section-button' + const copyIcon = document.createElement("i") + copyIcon.className = "fa-solid fa-clipboard section-button" copyIcon.innerHTML = `Copy Image Settings` - copyIcon.addEventListener('click', (event) => { + copyIcon.addEventListener("click", (event) => { event.stopPropagation() // Add css class 'active' - copyIcon.classList.add('active') + copyIcon.classList.add("active") // In 350 ms remove the 'active' class - asyncDelay(350).then(() => copyIcon.classList.remove('active')) + asyncDelay(350).then(() => copyIcon.classList.remove("active")) const uiState = readUI() TASK_REQ_NO_EXPORT.forEach((key) => delete uiState.reqBody[key]) if (uiState.reqBody.init_image && !IMAGE_REGEX.test(uiState.reqBody.init_image)) { @@ -641,8 +690,8 @@ function checkWriteToClipboardPermission (result) { } // Determine which access we have to the clipboard. Clipboard access is only available on localhost or via TLS. navigator.permissions.query({ name: "clipboard-write" }).then(checkWriteToClipboardPermission, (e) => { - if (e instanceof TypeError && typeof navigator?.clipboard?.writeText === 'function') { + if (e instanceof TypeError && typeof navigator?.clipboard?.writeText === "function") { // Fix for firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1560373 - checkWriteToClipboardPermission({state:"granted"}) + checkWriteToClipboardPermission({ state: "granted" }) } }) diff --git a/ui/media/js/engine.js b/ui/media/js/engine.js index 02871b23..a0eb9cef 100644 --- a/ui/media/js/engine.js +++ b/ui/media/js/engine.js @@ -1,6 +1,7 @@ /** SD-UI Backend control and classes. */ -(function () { "use strict"; +;(function() { + "use strict" const RETRY_DELAY_IF_BUFFER_IS_EMPTY = 1000 // ms const RETRY_DELAY_IF_SERVER_IS_BUSY = 30 * 1000 // ms, status_code 503, already a task running const RETRY_DELAY_ON_ERROR = 4000 // ms @@ -14,25 +15,28 @@ * Allows closing the connection while the server buffers more data. */ class ChunkedStreamReader { - #bufferedString = '' // Data received waiting to be read. + #bufferedString = "" // Data received waiting to be read. #url #fetchOptions #response - constructor(url, initialContent='', options={}) { - if (typeof url !== 'string' && !(url instanceof String)) { - throw new Error('url is not a string.') + constructor(url, initialContent = "", options = {}) { + if (typeof url !== "string" && !(url instanceof String)) { + throw new Error("url is not a string.") } - if (typeof initialContent !== 'undefined' && typeof initialContent !== 'string') { - throw new Error('initialContent is not a string.') + if (typeof initialContent !== "undefined" && typeof initialContent !== "string") { + throw new Error("initialContent is not a string.") } this.#bufferedString = initialContent this.#url = url - this.#fetchOptions = Object.assign({ - headers: { - 'Content-Type': 'application/json' - } - }, options) + this.#fetchOptions = Object.assign( + { + headers: { + "Content-Type": "application/json" + } + }, + options + ) this.onNext = undefined } @@ -53,7 +57,7 @@ } parse(value) { - if (typeof value === 'undefined') { + if (typeof value === "undefined") { return } if (!isArrayOrTypedArray(value)) { @@ -62,7 +66,7 @@ if (value.length === 0) { return value } - if (typeof this.textDecoder === 'undefined') { + if (typeof this.textDecoder === "undefined") { this.textDecoder = new TextDecoder() } return [this.textDecoder.decode(value)] @@ -73,8 +77,8 @@ onError(response) { throw new Error(response.statusText) } - onNext({value, done}, response) { - return {value, done} + onNext({ value, done }, response) { + return { value, done } } async *[Symbol.asyncIterator]() { @@ -93,9 +97,9 @@ continue } // Request status indicate failure - console.warn('Stream %o stopped unexpectedly.', this.#response) + console.warn("Stream %o stopped unexpectedly.", this.#response) value = await Promise.resolve(this.onError(this.#response)) - if (typeof value === 'boolean' && value) { + if (typeof value === "boolean" && value) { continue } return value @@ -106,8 +110,10 @@ const readState = await reader.read() value = this.parse(readState.value) if (value) { - for(let sVal of value) { - ({value: sVal, done} = await Promise.resolve(this.onNext({value:sVal, done:readState.done}))) + for (let sVal of value) { + ;({ value: sVal, done } = await Promise.resolve( + this.onNext({ value: sVal, done: readState.done }) + )) yield sVal if (done) { return this.onComplete(sVal) @@ -117,12 +123,12 @@ if (done) { return } - } while(value && !done) + } while (value && !done) } while (!done && (this.#response.ok || this.#response.status === 425)) } *readStreamAsJSON(jsonStr, throwOnError) { - if (typeof jsonStr !== 'string') { - throw new Error('jsonStr is not a string.') + if (typeof jsonStr !== "string") { + throw new Error("jsonStr is not a string.") } do { if (this.#bufferedString.length > 0) { @@ -132,19 +138,19 @@ } else { jsonStr = this.#bufferedString } - this.#bufferedString = '' + this.#bufferedString = "" } if (!jsonStr) { return } // Find next delimiter - let lastChunkIdx = jsonStr.indexOf('}{') + let lastChunkIdx = jsonStr.indexOf("}{") if (lastChunkIdx >= 0) { this.#bufferedString = jsonStr.substring(0, lastChunkIdx + 1) jsonStr = jsonStr.substring(lastChunkIdx + 1) } else { this.#bufferedString = jsonStr - jsonStr = '' + jsonStr = "" } if (this.#bufferedString.length <= 0) { return @@ -153,10 +159,11 @@ // this results in having to parse JSON like {"step": 1}{"step": 2}{"step": 3}{"ste... // which is obviously invalid and can happen at any point while rendering. // So we need to extract only the next {} section - try { // Try to parse + try { + // Try to parse const jsonObj = JSON.parse(this.#bufferedString) this.#bufferedString = jsonStr - jsonStr = '' + jsonStr = "" yield jsonObj } catch (e) { if (throwOnError) { @@ -168,18 +175,18 @@ } throw e } - } while (this.#bufferedString.length > 0 && this.#bufferedString.indexOf('}') >= 0) + } while (this.#bufferedString.length > 0 && this.#bufferedString.indexOf("}") >= 0) } } - const EVENT_IDLE = 'idle' - const EVENT_STATUS_CHANGED = 'statusChange' - const EVENT_UNHANDLED_REJECTION = 'unhandledRejection' - const EVENT_TASK_QUEUED = 'taskQueued' - const EVENT_TASK_START = 'taskStart' - const EVENT_TASK_END = 'taskEnd' - const EVENT_TASK_ERROR = 'task_error' - const EVENT_UNEXPECTED_RESPONSE = 'unexpectedResponse' + const EVENT_IDLE = "idle" + const EVENT_STATUS_CHANGED = "statusChange" + const EVENT_UNHANDLED_REJECTION = "unhandledRejection" + const EVENT_TASK_QUEUED = "taskQueued" + const EVENT_TASK_START = "taskStart" + const EVENT_TASK_END = "taskEnd" + const EVENT_TASK_ERROR = "task_error" + const EVENT_UNEXPECTED_RESPONSE = "unexpectedResponse" const EVENTS_TYPES = [ EVENT_IDLE, EVENT_STATUS_CHANGED, @@ -190,85 +197,86 @@ EVENT_TASK_END, EVENT_TASK_ERROR, - EVENT_UNEXPECTED_RESPONSE, + EVENT_UNEXPECTED_RESPONSE ] Object.freeze(EVENTS_TYPES) const eventSource = new GenericEventSource(EVENTS_TYPES) function setServerStatus(msgType, msg) { - return eventSource.fireEvent(EVENT_STATUS_CHANGED, {type: msgType, message: msg}) + return eventSource.fireEvent(EVENT_STATUS_CHANGED, { type: msgType, message: msg }) } const ServerStates = { - init: 'Init', - loadingModel: 'LoadingModel', - online: 'Online', - rendering: 'Rendering', - unavailable: 'Unavailable', + init: "Init", + loadingModel: "LoadingModel", + online: "Online", + rendering: "Rendering", + unavailable: "Unavailable" } Object.freeze(ServerStates) let sessionId = Date.now() - let serverState = {'status': ServerStates.unavailable, 'time': Date.now()} + let serverState = { status: ServerStates.unavailable, time: Date.now() } async function healthCheck() { - if (Date.now() < serverState.time + (HEALTH_PING_INTERVAL / 2) && isServerAvailable()) { + if (Date.now() < serverState.time + HEALTH_PING_INTERVAL / 2 && isServerAvailable()) { // Ping confirmed online less than half of HEALTH_PING_INTERVAL ago. return true } if (Date.now() >= serverState.time + SERVER_STATE_VALIDITY_DURATION) { - console.warn('WARNING! SERVER_STATE_VALIDITY_DURATION has elapsed since the last Ping completed.') + console.warn("WARNING! SERVER_STATE_VALIDITY_DURATION has elapsed since the last Ping completed.") } try { let res = undefined - if (typeof sessionId !== 'undefined') { - res = await fetch('/ping?session_id=' + sessionId) + if (typeof sessionId !== "undefined") { + res = await fetch("/ping?session_id=" + sessionId) } else { - res = await fetch('/ping') + res = await fetch("/ping") } serverState = await res.json() - if (typeof serverState !== 'object' || typeof serverState.status !== 'string') { + if (typeof serverState !== "object" || typeof serverState.status !== "string") { console.error(`Server reply didn't contain a state value.`) - serverState = {'status': ServerStates.unavailable, 'time': Date.now()} - setServerStatus('error', 'offline') + serverState = { status: ServerStates.unavailable, time: Date.now() } + setServerStatus("error", "offline") return false } // Set status - switch(serverState.status) { + switch (serverState.status) { case ServerStates.init: // Wait for init to complete before updating status. break case ServerStates.online: - setServerStatus('online', 'ready') + setServerStatus("online", "ready") break case ServerStates.loadingModel: - setServerStatus('busy', 'loading..') + setServerStatus("busy", "loading..") break case ServerStates.rendering: - setServerStatus('busy', 'rendering..') + setServerStatus("busy", "rendering..") break - default: // Unavailable - console.error('Ping received an unexpected server status. Status: %s', serverState.status) - setServerStatus('error', serverState.status.toLowerCase()) + default: + // Unavailable + console.error("Ping received an unexpected server status. Status: %s", serverState.status) + setServerStatus("error", serverState.status.toLowerCase()) break } serverState.time = Date.now() return true } catch (e) { console.error(e) - serverState = {'status': ServerStates.unavailable, 'time': Date.now()} - setServerStatus('error', 'offline') + serverState = { status: ServerStates.unavailable, time: Date.now() } + setServerStatus("error", "offline") } return false } function isServerAvailable() { - if (typeof serverState !== 'object') { - console.error('serverState not set to a value. Connection to server could be lost...') + if (typeof serverState !== "object") { + console.error("serverState not set to a value. Connection to server could be lost...") return false } if (Date.now() >= serverState.time + SERVER_STATE_VALIDITY_DURATION) { - console.warn('SERVER_STATE_VALIDITY_DURATION elapsed. Connection to server could be lost...') + console.warn("SERVER_STATE_VALIDITY_DURATION elapsed. Connection to server could be lost...") return false } switch (serverState.status) { @@ -277,51 +285,54 @@ case ServerStates.online: return true default: - console.warn('Unexpected server status. Server could be unavailable... Status: %s', serverState.status) + console.warn("Unexpected server status. Server could be unavailable... Status: %s", serverState.status) return false } } async function waitUntil(isReadyFn, delay, timeout) { - if (typeof delay === 'number') { + if (typeof delay === "number") { const msDelay = delay delay = () => asyncDelay(msDelay) } - if (typeof delay !== 'function') { - throw new Error('delay is not a number or a function.') + if (typeof delay !== "function") { + throw new Error("delay is not a number or a function.") } - if (typeof timeout !== 'undefined' && typeof timeout !== 'number') { - throw new Error('timeout is not a number.') + if (typeof timeout !== "undefined" && typeof timeout !== "number") { + throw new Error("timeout is not a number.") } - if (typeof timeout === 'undefined' || timeout < 0) { + if (typeof timeout === "undefined" || timeout < 0) { timeout = Number.MAX_SAFE_INTEGER } timeout = Date.now() + timeout - while (timeout > Date.now() - && Date.now() < serverState.time + SERVER_STATE_VALIDITY_DURATION - && !Boolean(await Promise.resolve(isReadyFn())) + while ( + timeout > Date.now() && + Date.now() < serverState.time + SERVER_STATE_VALIDITY_DURATION && + !Boolean(await Promise.resolve(isReadyFn())) ) { await delay() - if (!isServerAvailable()) { // Can fail if ping got frozen/suspended... - if (await healthCheck() && isServerAvailable()) { // Force a recheck of server status before failure... + if (!isServerAvailable()) { + // Can fail if ping got frozen/suspended... + if ((await healthCheck()) && isServerAvailable()) { + // Force a recheck of server status before failure... continue // Continue waiting if last healthCheck confirmed the server is still alive. } - throw new Error('Connection with server lost.') + throw new Error("Connection with server lost.") } } if (Date.now() >= serverState.time + SERVER_STATE_VALIDITY_DURATION) { - console.warn('SERVER_STATE_VALIDITY_DURATION elapsed. Released waitUntil on stale server state.') + console.warn("SERVER_STATE_VALIDITY_DURATION elapsed. Released waitUntil on stale server state.") } } const TaskStatus = { - init: 'init', - pending: 'pending', // Queued locally, not yet posted to server - waiting: 'waiting', // Waiting to run on server - processing: 'processing', - stopped: 'stopped', - completed: 'completed', - failed: 'failed', + init: "init", + pending: "pending", // Queued locally, not yet posted to server + waiting: "waiting", // Waiting to run on server + processing: "processing", + stopped: "stopped", + completed: "completed", + failed: "failed" } Object.freeze(TaskStatus) @@ -329,7 +340,7 @@ TaskStatus.init, TaskStatus.pending, TaskStatus.waiting, - TaskStatus.processing, + TaskStatus.processing //Don't add status that are final. ] @@ -345,12 +356,15 @@ #id = undefined #exception = undefined - constructor(options={}) { + constructor(options = {}) { this._reqBody = Object.assign({}, options) - if (typeof this._reqBody.session_id === 'undefined') { + if (typeof this._reqBody.session_id === "undefined") { this._reqBody.session_id = sessionId - } else if (this._reqBody.session_id !== SD.sessionId && String(this._reqBody.session_id) !== String(SD.sessionId)) { - throw new Error('Use SD.sessionId to set the request session_id.') + } else if ( + this._reqBody.session_id !== SD.sessionId && + String(this._reqBody.session_id) !== String(SD.sessionId) + ) { + throw new Error("Use SD.sessionId to set the request session_id.") } this._reqBody.session_id = String(this._reqBody.session_id) } @@ -359,8 +373,8 @@ return this.#id } _setId(id) { - if (typeof this.#id !== 'undefined') { - throw new Error('The task ID can only be set once.') + if (typeof this.#id !== "undefined") { + throw new Error("The task ID can only be set once.") } this.#id = id } @@ -372,32 +386,32 @@ if (this.isCompleted || this.isStopped || this.hasFailed) { return } - if (typeof exception !== 'undefined') { - if (typeof exception === 'string') { + if (typeof exception !== "undefined") { + if (typeof exception === "string") { exception = new Error(exception) } - if (typeof exception !== 'object') { - throw new Error('exception is not an object.') + if (typeof exception !== "object") { + throw new Error("exception is not an object.") } if (!(exception instanceof Error)) { - throw new Error('exception is not an Error or a string.') + throw new Error("exception is not an Error or a string.") } } - const res = await fetch('/image/stop?task=' + this.id) + const res = await fetch("/image/stop?task=" + this.id) if (!res.ok) { - console.log('Stop response:', res) + console.log("Stop response:", res) throw new Error(res.statusText) } task_queue.delete(this) this.#exception = exception - this.#status = (exception ? TaskStatus.failed : TaskStatus.stopped) + this.#status = exception ? TaskStatus.failed : TaskStatus.stopped } get reqBody() { if (this.#status === TaskStatus.init) { return this._reqBody } - console.warn('Task reqBody cannot be changed after the init state.') + console.warn("Task reqBody cannot be changed after the init state.") return Object.assign({}, this._reqBody) } @@ -436,29 +450,29 @@ * @returns the response from the render request. * @memberof Task */ - async post(url, timeout=-1) { - if(this.status !== TaskStatus.init && this.status !== TaskStatus.pending) { + async post(url, timeout = -1) { + if (this.status !== TaskStatus.init && this.status !== TaskStatus.pending) { throw new Error(`Task status ${this.status} is not valid for post.`) } this._setStatus(TaskStatus.pending) Object.freeze(this._reqBody) - const abortSignal = (timeout >= 0 ? AbortSignal.timeout(timeout) : undefined) + const abortSignal = timeout >= 0 ? AbortSignal.timeout(timeout) : undefined let res = undefined try { this.checkReqBody() do { abortSignal?.throwIfAborted() res = await fetch(url, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json' + "Content-Type": "application/json" }, body: JSON.stringify(this._reqBody), signal: abortSignal }) // status_code 503, already a task running. - } while (res.status === 503 && await asyncDelay(RETRY_DELAY_IF_SERVER_IS_BUSY)) + } while (res.status === 503 && (await asyncDelay(RETRY_DELAY_IF_SERVER_IS_BUSY))) } catch (err) { this.abort(err) throw err @@ -479,20 +493,20 @@ if (!value || value.length <= 0) { return } - return reader.readStreamAsJSON(value.join('')) + return reader.readStreamAsJSON(value.join("")) } - reader.onNext = function({done, value}) { + reader.onNext = function({ done, value }) { // By default is completed when the return value has a status defined. - if (typeof value === 'object' && 'status' in value) { + if (typeof value === "object" && "status" in value) { done = true } - return {done, value} + return { done, value } } return reader } _setReader(reader) { - if (typeof this.#reader !== 'undefined') { - throw new Error('The task reader can only be set once.') + if (typeof this.#reader !== "undefined") { + throw new Error("The task reader can only be set once.") } this.#reader = reader } @@ -501,25 +515,26 @@ return this.#reader } if (!this.streamUrl) { - throw new Error('The task has no stream Url defined.') + throw new Error("The task has no stream Url defined.") } this.#reader = Task.getReader(this.streamUrl) const task = this const onNext = this.#reader.onNext - this.#reader.onNext = function({done, value}) { - if (value && typeof value === 'object') { - if (task.status === TaskStatus.init - || task.status === TaskStatus.pending - || task.status === TaskStatus.waiting + this.#reader.onNext = function({ done, value }) { + if (value && typeof value === "object") { + if ( + task.status === TaskStatus.init || + task.status === TaskStatus.pending || + task.status === TaskStatus.waiting ) { task._setStatus(TaskStatus.processing) } - if ('step' in value && 'total_steps' in value) { + if ("step" in value && "total_steps" in value) { task.step = value.step task.total_steps = value.total_steps } } - return onNext.call(this, {done, value}) + return onNext.call(this, { done, value }) } this.#reader.onComplete = function(value) { task.result = value @@ -536,12 +551,12 @@ return this.#reader } - async waitUntil({timeout=-1, callback, status, signal}) { + async waitUntil({ timeout = -1, callback, status, signal }) { const currentIdx = TASK_STATUS_ORDER.indexOf(this.#status) if (currentIdx <= 0) { return false } - const stIdx = (status ? TASK_STATUS_ORDER.indexOf(status) : currentIdx + 1) + const stIdx = status ? TASK_STATUS_ORDER.indexOf(status) : currentIdx + 1 if (stIdx >= 0 && stIdx <= currentIdx) { return true } @@ -552,26 +567,34 @@ return false } const task = this - switch(this.#status) { + switch (this.#status) { case TaskStatus.pending: case TaskStatus.waiting: // Wait for server status to include this task. await waitUntil( async () => { - if (task.#id && typeof serverState.tasks === 'object' && Object.keys(serverState.tasks).includes(String(task.#id))) { + if ( + task.#id && + typeof serverState.tasks === "object" && + Object.keys(serverState.tasks).includes(String(task.#id)) + ) { return true } - if (await Promise.resolve(callback?.call(task)) || signal?.aborted) { + if ((await Promise.resolve(callback?.call(task))) || signal?.aborted) { return true } }, TASK_STATE_SERVER_UPDATE_DELAY, - timeout, + timeout ) - if (this.#id && typeof serverState.tasks === 'object' && Object.keys(serverState.tasks).includes(String(task.#id))) { + if ( + this.#id && + typeof serverState.tasks === "object" && + Object.keys(serverState.tasks).includes(String(task.#id)) + ) { this._setStatus(TaskStatus.waiting) } - if (await Promise.resolve(callback?.call(this)) || signal?.aborted) { + if ((await Promise.resolve(callback?.call(this))) || signal?.aborted) { return false } if (stIdx >= 0 && stIdx <= TASK_STATUS_ORDER.indexOf(TaskStatus.waiting)) { @@ -580,21 +603,25 @@ // Wait for task to start on server. await waitUntil( async () => { - if (typeof serverState.tasks !== 'object' || serverState.tasks[String(task.#id)] !== 'pending') { + if ( + typeof serverState.tasks !== "object" || + serverState.tasks[String(task.#id)] !== "pending" + ) { return true } - if (await Promise.resolve(callback?.call(task)) || signal?.aborted) { + if ((await Promise.resolve(callback?.call(task))) || signal?.aborted) { return true } }, TASK_STATE_SERVER_UPDATE_DELAY, - timeout, + timeout ) - const state = (typeof serverState.tasks === 'object' ? serverState.tasks[String(task.#id)] : undefined) - if (state === 'running' || state === 'buffer' || state === 'completed') { + const state = + typeof serverState.tasks === "object" ? serverState.tasks[String(task.#id)] : undefined + if (state === "running" || state === "buffer" || state === "completed") { this._setStatus(TaskStatus.processing) } - if (await Promise.resolve(callback?.call(task)) || signal?.aborted) { + if ((await Promise.resolve(callback?.call(task))) || signal?.aborted) { return false } if (stIdx >= 0 && stIdx <= TASK_STATUS_ORDER.indexOf(TaskStatus.processing)) { @@ -603,15 +630,18 @@ case TaskStatus.processing: await waitUntil( async () => { - if (typeof serverState.tasks !== 'object' || serverState.tasks[String(task.#id)] !== 'running') { + if ( + typeof serverState.tasks !== "object" || + serverState.tasks[String(task.#id)] !== "running" + ) { return true } - if (await Promise.resolve(callback?.call(task)) || signal?.aborted) { + if ((await Promise.resolve(callback?.call(task))) || signal?.aborted) { return true } }, TASK_STATE_SERVER_UPDATE_DELAY, - timeout, + timeout ) await Promise.resolve(callback?.call(this)) default: @@ -625,22 +655,22 @@ } this._setStatus(TaskStatus.pending) task_queue.set(this, promiseGenerator) - await eventSource.fireEvent(EVENT_TASK_QUEUED, {task:this}) + await eventSource.fireEvent(EVENT_TASK_QUEUED, { task: this }) await Task.enqueue(promiseGenerator, ...args) - await this.waitUntil({status: TaskStatus.completed}) + await this.waitUntil({ status: TaskStatus.completed }) if (this.exception) { throw this.exception } return this.result } static async enqueue(promiseGenerator, ...args) { - if (typeof promiseGenerator === 'undefined') { - throw new Error('To enqueue a concurrent task, a *Promise Generator is needed but undefined was found.') + if (typeof promiseGenerator === "undefined") { + throw new Error("To enqueue a concurrent task, a *Promise Generator is needed but undefined was found.") } //if (Symbol.asyncIterator in result || Symbol.iterator in result) { - //concurrent_generators.set(result, Promise.resolve(args)) - if (typeof promiseGenerator === 'function') { - concurrent_generators.set(asGenerator({callback: promiseGenerator}), Promise.resolve(args)) + //concurrent_generators.set(result, Promise.resolve(args)) + if (typeof promiseGenerator === "function") { + concurrent_generators.set(asGenerator({ callback: promiseGenerator }), Promise.resolve(args)) } else { concurrent_generators.set(promiseGenerator, Promise.resolve(args)) } @@ -649,23 +679,23 @@ } static enqueueNew(task, classCtor, progressCallback) { if (task.status !== TaskStatus.init) { - throw new Error('Task has an invalid status to add to queue.') + throw new Error("Task has an invalid status to add to queue.") } if (!(task instanceof classCtor)) { - throw new Error('Task is not a instance of classCtor.') + throw new Error("Task is not a instance of classCtor.") } let promiseGenerator = undefined - if (typeof progressCallback === 'undefined') { + if (typeof progressCallback === "undefined") { promiseGenerator = classCtor.start(task) - } else if (typeof progressCallback === 'function') { + } else if (typeof progressCallback === "function") { promiseGenerator = classCtor.start(task, progressCallback) } else { - throw new Error('progressCallback is not a function.') + throw new Error("progressCallback is not a function.") } return Task.prototype.enqueue.call(task, promiseGenerator) } - static async run(promiseGenerator, {callback, signal, timeout=-1}={}) { + static async run(promiseGenerator, { callback, signal, timeout = -1 } = {}) { let value = undefined let done = undefined if (timeout < 0) { @@ -673,20 +703,20 @@ } timeout = Date.now() + timeout do { - ({value, done} = await Promise.resolve(promiseGenerator.next(value))) + ;({ value, done } = await Promise.resolve(promiseGenerator.next(value))) if (value instanceof Promise) { value = await value } if (callback) { - ({value, done} = await Promise.resolve(callback.call(promiseGenerator, {value, done}))) + ;({ value, done } = await Promise.resolve(callback.call(promiseGenerator, { value, done }))) } if (value instanceof Promise) { value = await value } - } while(!done && !signal?.aborted && timeout > Date.now()) + } while (!done && !signal?.aborted && timeout > Date.now()) return value } - static async *asGenerator({callback, generator, signal, timeout=-1}={}) { + static async *asGenerator({ callback, generator, signal, timeout = -1 } = {}) { let value = undefined let done = undefined if (timeout < 0) { @@ -694,69 +724,69 @@ } timeout = Date.now() + timeout do { - ({value, done} = await Promise.resolve(generator.next(value))) + ;({ value, done } = await Promise.resolve(generator.next(value))) if (value instanceof Promise) { value = await value } if (callback) { - ({value, done} = await Promise.resolve(callback.call(generator, {value, done}))) + ;({ value, done } = await Promise.resolve(callback.call(generator, { value, done }))) if (value instanceof Promise) { value = await value } } value = yield value - } while(!done && !signal?.aborted && timeout > Date.now()) + } while (!done && !signal?.aborted && timeout > Date.now()) return value } } const TASK_REQUIRED = { - "session_id": 'string', - "prompt": 'string', - "negative_prompt": 'string', - "width": 'number', - "height": 'number', - "seed": 'number', + session_id: "string", + prompt: "string", + negative_prompt: "string", + width: "number", + height: "number", + seed: "number", - "sampler_name": 'string', - "use_stable_diffusion_model": 'string', - "num_inference_steps": 'number', - "guidance_scale": 'number', + sampler_name: "string", + use_stable_diffusion_model: "string", + num_inference_steps: "number", + guidance_scale: "number", - "num_outputs": 'number', - "stream_progress_updates": 'boolean', - "stream_image_progress": 'boolean', - "show_only_filtered_image": 'boolean', - "output_format": 'string', - "output_quality": 'number', + num_outputs: "number", + stream_progress_updates: "boolean", + stream_image_progress: "boolean", + show_only_filtered_image: "boolean", + output_format: "string", + output_quality: "number" } const TASK_DEFAULTS = { - "sampler_name": "plms", - "use_stable_diffusion_model": "sd-v1-4", - "num_inference_steps": 50, - "guidance_scale": 7.5, - "negative_prompt": "", + sampler_name: "plms", + use_stable_diffusion_model: "sd-v1-4", + num_inference_steps: 50, + guidance_scale: 7.5, + negative_prompt: "", - "num_outputs": 1, - "stream_progress_updates": true, - "stream_image_progress": true, - "show_only_filtered_image": true, - "block_nsfw": false, - "output_format": "png", - "output_quality": 75, - "output_lossless": false, + num_outputs: 1, + stream_progress_updates: true, + stream_image_progress: true, + show_only_filtered_image: true, + block_nsfw: false, + output_format: "png", + output_quality: 75, + output_lossless: false } const TASK_OPTIONAL = { - "device": 'string', - "init_image": 'string', - "mask": 'string', - "save_to_disk_path": 'string', - "use_face_correction": 'string', - "use_upscale": 'string', - "use_vae_model": 'string', - "use_hypernetwork_model": 'string', - "hypernetwork_strength": 'number', - "output_lossless": 'boolean', + device: "string", + init_image: "string", + mask: "string", + save_to_disk_path: "string", + use_face_correction: "string", + use_upscale: "string", + use_vae_model: "string", + use_hypernetwork_model: "string", + hypernetwork_strength: "number", + output_lossless: "boolean" } // Higer values will result in... @@ -764,36 +794,42 @@ const MAX_SEED_VALUE = 4294967295 class RenderTask extends Task { - constructor(options={}) { + constructor(options = {}) { super(options) - if (typeof this._reqBody.seed === 'undefined') { + if (typeof this._reqBody.seed === "undefined") { this._reqBody.seed = Math.floor(Math.random() * (MAX_SEED_VALUE + 1)) } - if (typeof typeof this._reqBody.seed === 'number' && (this._reqBody.seed > MAX_SEED_VALUE || this._reqBody.seed < 0)) { + if ( + typeof typeof this._reqBody.seed === "number" && + (this._reqBody.seed > MAX_SEED_VALUE || this._reqBody.seed < 0) + ) { throw new Error(`seed must be in range 0 to ${MAX_SEED_VALUE}.`) } - if ('use_cpu' in this._reqBody) { + if ("use_cpu" in this._reqBody) { if (this._reqBody.use_cpu) { - this._reqBody.device = 'cpu' + this._reqBody.device = "cpu" } delete this._reqBody.use_cpu } if (this._reqBody.init_image) { - if (typeof this._reqBody.prompt_strength === 'undefined') { + if (typeof this._reqBody.prompt_strength === "undefined") { this._reqBody.prompt_strength = 0.8 - } else if (typeof this._reqBody.prompt_strength !== 'number') { - throw new Error(`prompt_strength need to be of type number but ${typeof this._reqBody.prompt_strength} was found.`) + } else if (typeof this._reqBody.prompt_strength !== "number") { + throw new Error( + `prompt_strength need to be of type number but ${typeof this._reqBody + .prompt_strength} was found.` + ) } } - if ('modifiers' in this._reqBody) { + if ("modifiers" in this._reqBody) { if (Array.isArray(this._reqBody.modifiers) && this._reqBody.modifiers.length > 0) { this._reqBody.modifiers = this._reqBody.modifiers.filter((val) => val.trim()) if (this._reqBody.modifiers.length > 0) { - this._reqBody.prompt = `${this._reqBody.prompt}, ${this._reqBody.modifiers.join(', ')}` + this._reqBody.prompt = `${this._reqBody.prompt}, ${this._reqBody.modifiers.join(", ")}` } } - if (typeof this._reqBody.modifiers === 'string' && this._reqBody.modifiers.length > 0) { + if (typeof this._reqBody.modifiers === "string" && this._reqBody.modifiers.length > 0) { this._reqBody.modifiers = this._reqBody.modifiers.trim() if (this._reqBody.modifiers.length > 0) { this._reqBody.prompt = `${this._reqBody.prompt}, ${this._reqBody.modifiers}` @@ -806,13 +842,15 @@ checkReqBody() { for (const key in TASK_DEFAULTS) { - if (typeof this._reqBody[key] === 'undefined') { + if (typeof this._reqBody[key] === "undefined") { this._reqBody[key] = TASK_DEFAULTS[key] } } for (const key in TASK_REQUIRED) { if (typeof this._reqBody[key] !== TASK_REQUIRED[key]) { - throw new Error(`${key} need to be of type ${TASK_REQUIRED[key]} but ${typeof this._reqBody[key]} was found.`) + throw new Error( + `${key} need to be of type ${TASK_REQUIRED[key]} but ${typeof this._reqBody[key]} was found.` + ) } } for (const key in this._reqBody) { @@ -826,7 +864,11 @@ continue } if (typeof this._reqBody[key] !== TASK_OPTIONAL[key]) { - throw new Error(`${key} need to be of type ${TASK_OPTIONAL[key]} but ${typeof this._reqBody[key]} was found.`) + throw new Error( + `${key} need to be of type ${TASK_OPTIONAL[key]} but ${typeof this._reqBody[ + key + ]} was found.` + ) } } } @@ -837,23 +879,26 @@ * @returns the response from the render request. * @memberof Task */ - async post(timeout=-1) { - performance.mark('make-render-request') - if (performance.getEntriesByName('click-makeImage', 'mark').length > 0) { - performance.measure('diff', 'click-makeImage', 'make-render-request') - console.log('delay between clicking and making the server request:', performance.getEntriesByName('diff', 'measure')[0].duration + ' ms') + async post(timeout = -1) { + performance.mark("make-render-request") + if (performance.getEntriesByName("click-makeImage", "mark").length > 0) { + performance.measure("diff", "click-makeImage", "make-render-request") + console.log( + "delay between clicking and making the server request:", + performance.getEntriesByName("diff", "measure")[0].duration + " ms" + ) } - let jsonResponse = await super.post('/render', timeout) - if (typeof jsonResponse?.task !== 'number') { - console.warn('Endpoint error response: ', jsonResponse) - const event = Object.assign({task:this}, jsonResponse) + let jsonResponse = await super.post("/render", timeout) + if (typeof jsonResponse?.task !== "number") { + console.warn("Endpoint error response: ", jsonResponse) + const event = Object.assign({ task: this }, jsonResponse) await eventSource.fireEvent(EVENT_UNEXPECTED_RESPONSE, event) - if ('continueWith' in event) { + if ("continueWith" in event) { jsonResponse = await Promise.resolve(event.continueWith) } - if (typeof jsonResponse?.task !== 'number') { - const err = new Error(jsonResponse?.detail || 'Endpoint response does not contains a task ID.') + if (typeof jsonResponse?.task !== "number") { + const err = new Error(jsonResponse?.detail || "Endpoint response does not contains a task ID.") this.abort(err) throw err } @@ -870,71 +915,72 @@ return Task.enqueueNew(this, RenderTask, progressCallback) } *start(progressCallback) { - if (typeof progressCallback !== 'undefined' && typeof progressCallback !== 'function') { - throw new Error('progressCallback is not a function. progressCallback type: ' + typeof progressCallback) + if (typeof progressCallback !== "undefined" && typeof progressCallback !== "function") { + throw new Error("progressCallback is not a function. progressCallback type: " + typeof progressCallback) } if (this.isStopped) { return } this._setStatus(TaskStatus.pending) - progressCallback?.call(this, {reqBody: this._reqBody}) + progressCallback?.call(this, { reqBody: this._reqBody }) Object.freeze(this._reqBody) // Post task request to backend let renderRequest = undefined try { renderRequest = yield this.post() - yield progressCallback?.call(this, {renderResponse: renderRequest}) + yield progressCallback?.call(this, { renderResponse: renderRequest }) } catch (e) { yield progressCallback?.call(this, { detail: e.message }) throw e } - try { // Wait for task to start on server. + try { + // Wait for task to start on server. yield this.waitUntil({ - callback: function() { return progressCallback?.call(this, {}) }, - status: TaskStatus.processing, + callback: function() { + return progressCallback?.call(this, {}) + }, + status: TaskStatus.processing }) } catch (e) { this.abort(err) throw e } // Update class status and callback. - const taskState = (typeof serverState.tasks === 'object' ? serverState.tasks[String(this.id)] : undefined) - switch(taskState) { - case 'pending': // Session has pending tasks. - console.error('Server %o render request %o is still waiting.', serverState, renderRequest) + const taskState = typeof serverState.tasks === "object" ? serverState.tasks[String(this.id)] : undefined + switch (taskState) { + case "pending": // Session has pending tasks. + console.error("Server %o render request %o is still waiting.", serverState, renderRequest) //Only update status if not already set by waitUntil - if (this.status === TaskStatus.init - || this.status === TaskStatus.pending - ) { + if (this.status === TaskStatus.init || this.status === TaskStatus.pending) { // Set status as Waiting in backend. this._setStatus(TaskStatus.waiting) } break - case 'running': - case 'buffer': + case "running": + case "buffer": // Normal expected messages. this._setStatus(TaskStatus.processing) break - case 'completed': + case "completed": if (this.isPending) { // Set state to processing until we read the reply this._setStatus(TaskStatus.processing) } - console.warn('Server %o render request %o completed unexpectedly', serverState, renderRequest) + console.warn("Server %o render request %o completed unexpectedly", serverState, renderRequest) break // Continue anyway to try to read cached result. - case 'error': + case "error": this._setStatus(TaskStatus.failed) - console.error('Server %o render request %o has failed', serverState, renderRequest) + console.error("Server %o render request %o has failed", serverState, renderRequest) break // Still valid, Update UI with error message - case 'stopped': + case "stopped": this._setStatus(TaskStatus.stopped) - console.log('Server %o render request %o was stopped', serverState, renderRequest) + console.log("Server %o render request %o was stopped", serverState, renderRequest) return false default: if (!progressCallback) { - const err = new Error('Unexpected server task state: ' + taskState || 'Undefined') + const err = new Error("Unexpected server task state: " + taskState || "Undefined") this.abort(err) throw err } @@ -967,17 +1013,17 @@ let done = undefined yield progressCallback?.call(this, { stream: streamGenerator }) do { - ({value, done} = yield streamGenerator.next()) - if (typeof value !== 'object') { + ;({ value, done } = yield streamGenerator.next()) + if (typeof value !== "object") { continue } yield progressCallback?.call(this, { update: value }) - } while(!done) + } while (!done) return value } static start(task, progressCallback) { - if (typeof task !== 'object') { - throw new Error ('task is not an object. task type: ' + typeof task) + if (typeof task !== "object") { + throw new Error("task is not an object. task type: " + typeof task) } if (!(task instanceof Task)) { if (task.reqBody) { @@ -994,15 +1040,14 @@ } } class FilterTask extends Task { - constructor(options={}) { - } + constructor(options = {}) {} /** Send current task to server. * @param {*} [timeout=-1] Optional timeout value in ms * @returns the response from the render request. * @memberof Task */ - async post(timeout=-1) { - let jsonResponse = await super.post('/filter', timeout) + async post(timeout = -1) { + let jsonResponse = await super.post("/filter", timeout) //this._setId(jsonResponse.task) this._setStatus(TaskStatus.waiting) } @@ -1010,16 +1055,16 @@ return Task.enqueueNew(this, FilterTask, progressCallback) } *start(progressCallback) { - if (typeof progressCallback !== 'undefined' && typeof progressCallback !== 'function') { - throw new Error('progressCallback is not a function. progressCallback type: ' + typeof progressCallback) + if (typeof progressCallback !== "undefined" && typeof progressCallback !== "function") { + throw new Error("progressCallback is not a function. progressCallback type: " + typeof progressCallback) } if (this.isStopped) { return } } static start(task, progressCallback) { - if (typeof task !== 'object') { - throw new Error ('task is not an object. task type: ' + typeof task) + if (typeof task !== "object") { + throw new Error("task is not an object. task type: " + typeof task) } if (!(task instanceof Task)) { if (task.reqBody) { @@ -1036,26 +1081,30 @@ } } - const getSystemInfo = debounce(async function() { - let systemInfo = { - devices: { - all: {}, - active: {}, - }, - hosts: [] - } - try { - const res = await fetch('/get/system_info') - if (!res.ok) { - console.error('Invalid response fetching devices', res.statusText) - return systemInfo + const getSystemInfo = debounce( + async function() { + let systemInfo = { + devices: { + all: {}, + active: {} + }, + hosts: [] } - systemInfo = await res.json() - } catch (e) { - console.error('error fetching system info', e) - } - return systemInfo - }, 250, true) + try { + const res = await fetch("/get/system_info") + if (!res.ok) { + console.error("Invalid response fetching devices", res.statusText) + return systemInfo + } + systemInfo = await res.json() + } catch (e) { + console.error("error fetching system info", e) + } + return systemInfo + }, + 250, + true + ) async function getDevices() { let systemInfo = getSystemInfo() return systemInfo.devices @@ -1067,26 +1116,26 @@ async function getModels() { let models = { - 'stable-diffusion': [], - 'vae': [], + "stable-diffusion": [], + vae: [] } try { - const res = await fetch('/get/models') + const res = await fetch("/get/models") if (!res.ok) { - console.error('Invalid response fetching models', res.statusText) + console.error("Invalid response fetching models", res.statusText) return models } models = await res.json() - console.log('get models response', models) + console.log("get models response", models) } catch (e) { - console.log('get models error', e) + console.log("get models error", e) } return models } function getServerCapacity() { let activeDevicesCount = Object.keys(serverState?.devices?.active || {}).length - if (typeof window === "object" && window.document.visibilityState === 'hidden') { + if (typeof window === "object" && window.document.visibilityState === "hidden") { activeDevicesCount = 1 + activeDevicesCount } return activeDevicesCount @@ -1094,7 +1143,7 @@ let idleEventPromise = undefined function continueTasks() { - if (typeof navigator?.scheduling?.isInputPending === 'function') { + if (typeof navigator?.scheduling?.isInputPending === "function") { const inputPendingOptions = { // Report mouse/pointer move events when queue is empty. // Delay idle after mouse moves stops. @@ -1108,7 +1157,9 @@ const serverCapacity = getServerCapacity() if (task_queue.size <= 0 && concurrent_generators.size <= 0) { if (!idleEventPromise?.isPending) { - idleEventPromise = makeQuerablePromise(eventSource.fireEvent(EVENT_IDLE, {capacity: serverCapacity, idle: true})) + idleEventPromise = makeQuerablePromise( + eventSource.fireEvent(EVENT_IDLE, { capacity: serverCapacity, idle: true }) + ) } // Calling idle could result in task being added to queue. // if (task_queue.size <= 0 && concurrent_generators.size <= 0) { @@ -1117,7 +1168,9 @@ } if (task_queue.size < serverCapacity) { if (!idleEventPromise?.isPending) { - idleEventPromise = makeQuerablePromise(eventSource.fireEvent(EVENT_IDLE, {capacity: serverCapacity - task_queue.size})) + idleEventPromise = makeQuerablePromise( + eventSource.fireEvent(EVENT_IDLE, { capacity: serverCapacity - task_queue.size }) + ) } } const completedTasks = [] @@ -1128,25 +1181,25 @@ let value = promise.resolvedValue?.value || promise.resolvedValue if (promise.isRejected) { console.error(promise.rejectReason) - const event = {generator, reason: promise.rejectReason} + const event = { generator, reason: promise.rejectReason } eventSource.fireEvent(EVENT_UNHANDLED_REJECTION, event) - if ('continueWith' in event) { + if ("continueWith" in event) { value = Promise.resolve(event.continueWith) } else { concurrent_generators.delete(generator) - completedTasks.push({generator, promise}) + completedTasks.push({ generator, promise }) continue } } if (value instanceof Promise) { - promise = makeQuerablePromise(value.then((val) => ({done: promise.resolvedValue?.done, value: val}))) + promise = makeQuerablePromise(value.then((val) => ({ done: promise.resolvedValue?.done, value: val }))) concurrent_generators.set(generator, promise) continue } weak_results.set(generator, value) if (promise.resolvedValue?.done) { concurrent_generators.delete(generator) - completedTasks.push({generator, promise}) + completedTasks.push({ generator, promise }) continue } @@ -1161,12 +1214,16 @@ for (let [task, generator] of task_queue.entries()) { const cTsk = completedTasks.find((item) => item.generator === generator) if (cTsk?.promise?.rejectReason || task.hasFailed) { - eventSource.fireEvent(EVENT_TASK_ERROR, {task, generator, reason: cTsk?.promise?.rejectReason || task.exception }) + eventSource.fireEvent(EVENT_TASK_ERROR, { + task, + generator, + reason: cTsk?.promise?.rejectReason || task.exception + }) task_queue.delete(task) continue } if (task.isCompleted || task.isStopped || cTsk) { - const eventEndArgs = {task, generator} + const eventEndArgs = { task, generator } if (task.isStopped) { eventEndArgs.stopped = true } @@ -1178,13 +1235,13 @@ break } if (!generator) { - if (typeof task.start === 'function') { + if (typeof task.start === "function") { generator = task.start() } } else if (concurrent_generators.has(generator)) { continue } - const event = {task, generator}; + const event = { task, generator } const beforeStart = eventSource.fireEvent(EVENT_TASK_START, event) // optional beforeStart promise to wait on before starting task. const promise = makeQuerablePromise(beforeStart.then(() => Promise.resolve(event.beforeStart))) concurrent_generators.set(event.generator, promise) @@ -1206,16 +1263,16 @@ taskPromise = makeQuerablePromise(taskPromise.resolvedValue) continue } - if (typeof navigator?.scheduling?.isInputPending === 'function' && navigator.scheduling.isInputPending()) { + if (typeof navigator?.scheduling?.isInputPending === "function" && navigator.scheduling.isInputPending()) { return } const continuePromise = continueTasks().catch(async function(err) { console.error(err) - await eventSource.fireEvent(EVENT_UNHANDLED_REJECTION, {reason: err}) + await eventSource.fireEvent(EVENT_UNHANDLED_REJECTION, { reason: err }) await asyncDelay(RETRY_DELAY_ON_ERROR) }) taskPromise = makeQuerablePromise(continuePromise) - } while(taskPromise?.isResolved) + } while (taskPromise?.isResolved) } const SD = { @@ -1227,8 +1284,8 @@ FilterTask, Events: EVENTS_TYPES, - init: async function(options={}) { - if ('events' in options) { + init: async function(options = {}) { + if ("events" in options) { for (const key in options.events) { eventSource.addEventListener(key, options.events[key]) } @@ -1256,55 +1313,56 @@ render: (...args) => RenderTask.run(...args), filter: (...args) => FilterTask.run(...args), - waitUntil, - }; + waitUntil + } Object.defineProperties(SD, { serverState: { configurable: false, - get: () => serverState, + get: () => serverState }, isAvailable: { configurable: false, - get: () => isServerAvailable(), + get: () => isServerAvailable() }, serverCapacity: { configurable: false, - get: () => getServerCapacity(), + get: () => getServerCapacity() }, sessionId: { configurable: false, get: () => sessionId, set: (val) => { - if (typeof val === 'undefined') { + if (typeof val === "undefined") { throw new Error("Can't set sessionId to undefined.") } sessionId = val - }, + } }, MAX_SEED_VALUE: { configurable: false, - get: () => MAX_SEED_VALUE, + get: () => MAX_SEED_VALUE }, activeTasks: { configurable: false, - get: () => task_queue, - }, + get: () => task_queue + } }) Object.defineProperties(getGlobal(), { SD: { configurable: false, - get: () => SD, + get: () => SD }, - sessionId: { //TODO Remove in the future in favor of SD.sessionId + sessionId: { + //TODO Remove in the future in favor of SD.sessionId configurable: false, get: () => { - console.warn('Deprecated window.sessionId has been replaced with SD.sessionId.') + console.warn("Deprecated window.sessionId has been replaced with SD.sessionId.") console.trace() return SD.sessionId }, set: (val) => { - console.warn('Deprecated window.sessionId has been replaced with SD.sessionId.') + console.warn("Deprecated window.sessionId has been replaced with SD.sessionId.") console.trace() SD.sessionId = val } diff --git a/ui/media/js/image-editor.js b/ui/media/js/image-editor.js index b095d54d..e9f766c8 100644 --- a/ui/media/js/image-editor.js +++ b/ui/media/js/image-editor.js @@ -3,764 +3,796 @@ var editorControlsLeft = document.getElementById("image-editor-controls-left") const IMAGE_EDITOR_MAX_SIZE = 800 const IMAGE_EDITOR_BUTTONS = [ - { - name: "Cancel", - icon: "fa-regular fa-circle-xmark", - handler: editor => { - editor.hide() - } - }, - { - name: "Save", - icon: "fa-solid fa-floppy-disk", - handler: editor => { - editor.saveImage() - } - } + { + name: "Cancel", + icon: "fa-regular fa-circle-xmark", + handler: (editor) => { + editor.hide() + } + }, + { + name: "Save", + icon: "fa-solid fa-floppy-disk", + handler: (editor) => { + editor.saveImage() + } + } ] const defaultToolBegin = (editor, ctx, x, y, is_overlay = false) => { - ctx.beginPath() - ctx.moveTo(x, y) + ctx.beginPath() + ctx.moveTo(x, y) } const defaultToolMove = (editor, ctx, x, y, is_overlay = false) => { - ctx.lineTo(x, y) - if (is_overlay) { - ctx.clearRect(0, 0, editor.width, editor.height) - ctx.stroke() - } + ctx.lineTo(x, y) + if (is_overlay) { + ctx.clearRect(0, 0, editor.width, editor.height) + ctx.stroke() + } } const defaultToolEnd = (editor, ctx, x, y, is_overlay = false) => { - ctx.stroke() - if (is_overlay) { - ctx.clearRect(0, 0, editor.width, editor.height) - } + ctx.stroke() + if (is_overlay) { + ctx.clearRect(0, 0, editor.width, editor.height) + } } const toolDoNothing = (editor, ctx, x, y, is_overlay = false) => {} const IMAGE_EDITOR_TOOLS = [ - { - id: "draw", - name: "Draw", - icon: "fa-solid fa-pencil", - cursor: "url(/media/images/fa-pencil.svg) 0 24, pointer", - begin: defaultToolBegin, - move: defaultToolMove, - end: defaultToolEnd - }, - { - id: "erase", - name: "Erase", - icon: "fa-solid fa-eraser", - cursor: "url(/media/images/fa-eraser.svg) 0 14, pointer", - begin: defaultToolBegin, - move: (editor, ctx, x, y, is_overlay = false) => { - ctx.lineTo(x, y) - if (is_overlay) { - ctx.clearRect(0, 0, editor.width, editor.height) - ctx.globalCompositeOperation = "source-over" - ctx.globalAlpha = 1 - ctx.filter = "none" - ctx.drawImage(editor.canvas_current, 0, 0) - editor.setBrush(editor.layers.overlay) - ctx.stroke() - editor.canvas_current.style.opacity = 0 - } - }, - end: (editor, ctx, x, y, is_overlay = false) => { - ctx.stroke() - if (is_overlay) { - ctx.clearRect(0, 0, editor.width, editor.height) - editor.canvas_current.style.opacity = "" - } - }, - setBrush: (editor, layer) => { - layer.ctx.globalCompositeOperation = "destination-out" - } - }, - { - id: "fill", - name: "Fill", - icon: "fa-solid fa-fill", - cursor: "url(/media/images/fa-fill.svg) 20 6, pointer", - begin: (editor, ctx, x, y, is_overlay = false) => { - if (!is_overlay) { - var color = hexToRgb(ctx.fillStyle) - color.a = parseInt(ctx.globalAlpha * 255) // layer.ctx.globalAlpha - flood_fill(editor, ctx, parseInt(x), parseInt(y), color) - } - }, - move: toolDoNothing, - end: toolDoNothing - }, - { - id: "colorpicker", - name: "Picker", - icon: "fa-solid fa-eye-dropper", - cursor: "url(/media/images/fa-eye-dropper.svg) 0 24, pointer", - begin: (editor, ctx, x, y, is_overlay = false) => { - if (!is_overlay) { - var img_rgb = editor.layers.background.ctx.getImageData(x, y, 1, 1).data - var drawn_rgb = editor.ctx_current.getImageData(x, y, 1, 1).data - var drawn_opacity = drawn_rgb[3] / 255 - editor.custom_color_input.value = rgbToHex({ - r: (drawn_rgb[0] * drawn_opacity) + (img_rgb[0] * (1 - drawn_opacity)), - g: (drawn_rgb[1] * drawn_opacity) + (img_rgb[1] * (1 - drawn_opacity)), - b: (drawn_rgb[2] * drawn_opacity) + (img_rgb[2] * (1 - drawn_opacity)), - }) - editor.custom_color_input.dispatchEvent(new Event("change")) - } - }, - move: toolDoNothing, - end: toolDoNothing - } + { + id: "draw", + name: "Draw", + icon: "fa-solid fa-pencil", + cursor: "url(/media/images/fa-pencil.svg) 0 24, pointer", + begin: defaultToolBegin, + move: defaultToolMove, + end: defaultToolEnd + }, + { + id: "erase", + name: "Erase", + icon: "fa-solid fa-eraser", + cursor: "url(/media/images/fa-eraser.svg) 0 14, pointer", + begin: defaultToolBegin, + move: (editor, ctx, x, y, is_overlay = false) => { + ctx.lineTo(x, y) + if (is_overlay) { + ctx.clearRect(0, 0, editor.width, editor.height) + ctx.globalCompositeOperation = "source-over" + ctx.globalAlpha = 1 + ctx.filter = "none" + ctx.drawImage(editor.canvas_current, 0, 0) + editor.setBrush(editor.layers.overlay) + ctx.stroke() + editor.canvas_current.style.opacity = 0 + } + }, + end: (editor, ctx, x, y, is_overlay = false) => { + ctx.stroke() + if (is_overlay) { + ctx.clearRect(0, 0, editor.width, editor.height) + editor.canvas_current.style.opacity = "" + } + }, + setBrush: (editor, layer) => { + layer.ctx.globalCompositeOperation = "destination-out" + } + }, + { + id: "fill", + name: "Fill", + icon: "fa-solid fa-fill", + cursor: "url(/media/images/fa-fill.svg) 20 6, pointer", + begin: (editor, ctx, x, y, is_overlay = false) => { + if (!is_overlay) { + var color = hexToRgb(ctx.fillStyle) + color.a = parseInt(ctx.globalAlpha * 255) // layer.ctx.globalAlpha + flood_fill(editor, ctx, parseInt(x), parseInt(y), color) + } + }, + move: toolDoNothing, + end: toolDoNothing + }, + { + id: "colorpicker", + name: "Picker", + icon: "fa-solid fa-eye-dropper", + cursor: "url(/media/images/fa-eye-dropper.svg) 0 24, pointer", + begin: (editor, ctx, x, y, is_overlay = false) => { + if (!is_overlay) { + var img_rgb = editor.layers.background.ctx.getImageData(x, y, 1, 1).data + var drawn_rgb = editor.ctx_current.getImageData(x, y, 1, 1).data + var drawn_opacity = drawn_rgb[3] / 255 + editor.custom_color_input.value = rgbToHex({ + r: drawn_rgb[0] * drawn_opacity + img_rgb[0] * (1 - drawn_opacity), + g: drawn_rgb[1] * drawn_opacity + img_rgb[1] * (1 - drawn_opacity), + b: drawn_rgb[2] * drawn_opacity + img_rgb[2] * (1 - drawn_opacity) + }) + editor.custom_color_input.dispatchEvent(new Event("change")) + } + }, + move: toolDoNothing, + end: toolDoNothing + } ] const IMAGE_EDITOR_ACTIONS = [ - { - id: "load_mask", - name: "Load mask from file", - className: "load_mask", - icon: "fa-regular fa-folder-open", - handler: (editor) => { - let el = document.createElement('input') - el.setAttribute("type", "file") - el.addEventListener("change", function() { - if (this.files.length === 0) { - return - } + { + id: "load_mask", + name: "Load mask from file", + className: "load_mask", + icon: "fa-regular fa-folder-open", + handler: (editor) => { + let el = document.createElement("input") + el.setAttribute("type", "file") + el.addEventListener("change", function() { + if (this.files.length === 0) { + return + } - let reader = new FileReader() - let file = this.files[0] + let reader = new FileReader() + let file = this.files[0] - reader.addEventListener('load', function(event) { - let maskData = reader.result + reader.addEventListener("load", function(event) { + let maskData = reader.result - editor.layers.drawing.ctx.clearRect(0, 0, editor.width, editor.height) - var image = new Image() - image.onload = () => { - editor.layers.drawing.ctx.drawImage(image, 0, 0, editor.width, editor.height) - } - image.src = maskData - }) + editor.layers.drawing.ctx.clearRect(0, 0, editor.width, editor.height) + var image = new Image() + image.onload = () => { + editor.layers.drawing.ctx.drawImage(image, 0, 0, editor.width, editor.height) + } + image.src = maskData + }) - if (file) { - reader.readAsDataURL(file) - } - }) + if (file) { + reader.readAsDataURL(file) + } + }) - el.click() - }, - trackHistory: true - }, - { - id: "fill_all", - name: "Fill all", - icon: "fa-solid fa-paint-roller", - handler: (editor) => { - editor.ctx_current.globalCompositeOperation = "source-over" - editor.ctx_current.rect(0, 0, editor.width, editor.height) - editor.ctx_current.fill() - editor.setBrush() - }, - trackHistory: true - }, - { - id: "clear", - name: "Clear", - icon: "fa-solid fa-xmark", - handler: (editor) => { - editor.ctx_current.clearRect(0, 0, editor.width, editor.height) - imageEditor.setImage(null, editor.width, editor.height) // properly reset the drawing canvas - }, - trackHistory: true - }, - { - id: "undo", - name: "Undo", - icon: "fa-solid fa-rotate-left", - handler: (editor) => { - editor.history.undo() - }, - trackHistory: false - }, - { - id: "redo", - name: "Redo", - icon: "fa-solid fa-rotate-right", - handler: (editor) => { - editor.history.redo() - }, - trackHistory: false - } + el.click() + }, + trackHistory: true + }, + { + id: "fill_all", + name: "Fill all", + icon: "fa-solid fa-paint-roller", + handler: (editor) => { + editor.ctx_current.globalCompositeOperation = "source-over" + editor.ctx_current.rect(0, 0, editor.width, editor.height) + editor.ctx_current.fill() + editor.setBrush() + }, + trackHistory: true + }, + { + id: "clear", + name: "Clear", + icon: "fa-solid fa-xmark", + handler: (editor) => { + editor.ctx_current.clearRect(0, 0, editor.width, editor.height) + imageEditor.setImage(null, editor.width, editor.height) // properly reset the drawing canvas + }, + trackHistory: true + }, + { + id: "undo", + name: "Undo", + icon: "fa-solid fa-rotate-left", + handler: (editor) => { + editor.history.undo() + }, + trackHistory: false + }, + { + id: "redo", + name: "Redo", + icon: "fa-solid fa-rotate-right", + handler: (editor) => { + editor.history.redo() + }, + trackHistory: false + } ] var IMAGE_EDITOR_SECTIONS = [ - { - name: "tool", - title: "Tool", - default: "draw", - options: Array.from(IMAGE_EDITOR_TOOLS.map(t => t.id)), - initElement: (element, option) => { - var tool_info = IMAGE_EDITOR_TOOLS.find(t => t.id == option) - element.className = "image-editor-button button" - var sub_element = document.createElement("div") - var icon = document.createElement("i") - tool_info.icon.split(" ").forEach(c => icon.classList.add(c)) - sub_element.appendChild(icon) - sub_element.append(tool_info.name) - element.appendChild(sub_element) - } - }, - { - name: "color", - title: "Color", - default: "#f1c232", - options: [ - "custom", - "#ea9999", "#e06666", "#cc0000", "#990000", "#660000", - "#f9cb9c", "#f6b26b", "#e69138", "#b45f06", "#783f04", - "#ffe599", "#ffd966", "#f1c232", "#bf9000", "#7f6000", - "#b6d7a8", "#93c47d", "#6aa84f", "#38761d", "#274e13", - "#a4c2f4", "#6d9eeb", "#3c78d8", "#1155cc", "#1c4587", - "#b4a7d6", "#8e7cc3", "#674ea7", "#351c75", "#20124d", - "#d5a6bd", "#c27ba0", "#a64d79", "#741b47", "#4c1130", - "#ffffff", "#c0c0c0", "#838383", "#525252", "#000000", - ], - initElement: (element, option) => { - if (option == "custom") { - var input = document.createElement("input") - input.type = "color" - element.appendChild(input) - var span = document.createElement("span") - span.textContent = "Custom" - span.onclick = function(e) { - input.click() - } - element.appendChild(span) - } - else { - element.style.background = option - } - }, - getCustom: editor => { - var input = editor.popup.querySelector(".image_editor_color input") - return input.value - } - }, - { - name: "brush_size", - title: "Brush Size", - default: 48, - options: [ 6, 12, 16, 24, 30, 40, 48, 64 ], - initElement: (element, option) => { - element.parentElement.style.flex = option - element.style.width = option + "px" - element.style.height = option + "px" - element.style['margin-right'] = '2px' - element.style["border-radius"] = (option / 2).toFixed() + "px" - } - }, - { - name: "opacity", - title: "Opacity", - default: 0, - options: [ 0, 0.2, 0.4, 0.6, 0.8 ], - initElement: (element, option) => { - element.style.background = `repeating-conic-gradient(rgba(0, 0, 0, ${option}) 0% 25%, rgba(255, 255, 255, ${option}) 0% 50%) 50% / 10px 10px` - } - }, - { - name: "sharpness", - title: "Sharpness", - default: 0, - options: [ 0, 0.05, 0.1, 0.2, 0.3 ], - initElement: (element, option) => { - var size = 32 - var blur_amount = parseInt(option * size) - var sub_element = document.createElement("div") - sub_element.style.background = `var(--background-color3)` - sub_element.style.filter = `blur(${blur_amount}px)` - sub_element.style.width = `${size - 2}px` - sub_element.style.height = `${size - 2}px` - sub_element.style['border-radius'] = `${size}px` - element.style.background = "none" - element.appendChild(sub_element) - } - } + { + name: "tool", + title: "Tool", + default: "draw", + options: Array.from(IMAGE_EDITOR_TOOLS.map((t) => t.id)), + initElement: (element, option) => { + var tool_info = IMAGE_EDITOR_TOOLS.find((t) => t.id == option) + element.className = "image-editor-button button" + var sub_element = document.createElement("div") + var icon = document.createElement("i") + tool_info.icon.split(" ").forEach((c) => icon.classList.add(c)) + sub_element.appendChild(icon) + sub_element.append(tool_info.name) + element.appendChild(sub_element) + } + }, + { + name: "color", + title: "Color", + default: "#f1c232", + options: [ + "custom", + "#ea9999", + "#e06666", + "#cc0000", + "#990000", + "#660000", + "#f9cb9c", + "#f6b26b", + "#e69138", + "#b45f06", + "#783f04", + "#ffe599", + "#ffd966", + "#f1c232", + "#bf9000", + "#7f6000", + "#b6d7a8", + "#93c47d", + "#6aa84f", + "#38761d", + "#274e13", + "#a4c2f4", + "#6d9eeb", + "#3c78d8", + "#1155cc", + "#1c4587", + "#b4a7d6", + "#8e7cc3", + "#674ea7", + "#351c75", + "#20124d", + "#d5a6bd", + "#c27ba0", + "#a64d79", + "#741b47", + "#4c1130", + "#ffffff", + "#c0c0c0", + "#838383", + "#525252", + "#000000" + ], + initElement: (element, option) => { + if (option == "custom") { + var input = document.createElement("input") + input.type = "color" + element.appendChild(input) + var span = document.createElement("span") + span.textContent = "Custom" + span.onclick = function(e) { + input.click() + } + element.appendChild(span) + } else { + element.style.background = option + } + }, + getCustom: (editor) => { + var input = editor.popup.querySelector(".image_editor_color input") + return input.value + } + }, + { + name: "brush_size", + title: "Brush Size", + default: 48, + options: [6, 12, 16, 24, 30, 40, 48, 64], + initElement: (element, option) => { + element.parentElement.style.flex = option + element.style.width = option + "px" + element.style.height = option + "px" + element.style["margin-right"] = "2px" + element.style["border-radius"] = (option / 2).toFixed() + "px" + } + }, + { + name: "opacity", + title: "Opacity", + default: 0, + options: [0, 0.2, 0.4, 0.6, 0.8], + initElement: (element, option) => { + element.style.background = `repeating-conic-gradient(rgba(0, 0, 0, ${option}) 0% 25%, rgba(255, 255, 255, ${option}) 0% 50%) 50% / 10px 10px` + } + }, + { + name: "sharpness", + title: "Sharpness", + default: 0, + options: [0, 0.05, 0.1, 0.2, 0.3], + initElement: (element, option) => { + var size = 32 + var blur_amount = parseInt(option * size) + var sub_element = document.createElement("div") + sub_element.style.background = `var(--background-color3)` + sub_element.style.filter = `blur(${blur_amount}px)` + sub_element.style.width = `${size - 2}px` + sub_element.style.height = `${size - 2}px` + sub_element.style["border-radius"] = `${size}px` + element.style.background = "none" + element.appendChild(sub_element) + } + } ] class EditorHistory { - constructor(editor) { - this.editor = editor - this.events = [] // stack of all events (actions/edits) - this.current_edit = null - this.rewind_index = 0 // how many events back into the history we've rewound to. (current state is just after event at index 'length - this.rewind_index - 1') - } - push(event) { - // probably add something here eventually to save state every x events - if (this.rewind_index != 0) { - this.events = this.events.slice(0, 0 - this.rewind_index) - this.rewind_index = 0 - } - var snapshot_frequency = 20 // (every x edits, take a snapshot of the current drawing state, for faster rewinding) - if (this.events.length > 0 && this.events.length % snapshot_frequency == 0) { - event.snapshot = this.editor.layers.drawing.ctx.getImageData(0, 0, this.editor.width, this.editor.height) - } - this.events.push(event) - } - pushAction(action) { - this.push({ - type: "action", - id: action - }); - } - editBegin(x, y) { - this.current_edit = { - type: "edit", - id: this.editor.getOptionValue("tool"), - options: Object.assign({}, this.editor.options), - points: [ { x: x, y: y } ] - } - } - editMove(x, y) { - if (this.current_edit) { - this.current_edit.points.push({ x: x, y: y }) - } - } - editEnd(x, y) { - if (this.current_edit) { - this.push(this.current_edit) - this.current_edit = null - } - } - clear() { - this.events = [] - } - undo() { - this.rewindTo(this.rewind_index + 1) - } - redo() { - this.rewindTo(this.rewind_index - 1) - } - rewindTo(new_rewind_index) { - if (new_rewind_index < 0 || new_rewind_index > this.events.length) { - return; // do nothing if target index is out of bounds - } + constructor(editor) { + this.editor = editor + this.events = [] // stack of all events (actions/edits) + this.current_edit = null + this.rewind_index = 0 // how many events back into the history we've rewound to. (current state is just after event at index 'length - this.rewind_index - 1') + } + push(event) { + // probably add something here eventually to save state every x events + if (this.rewind_index != 0) { + this.events = this.events.slice(0, 0 - this.rewind_index) + this.rewind_index = 0 + } + var snapshot_frequency = 20 // (every x edits, take a snapshot of the current drawing state, for faster rewinding) + if (this.events.length > 0 && this.events.length % snapshot_frequency == 0) { + event.snapshot = this.editor.layers.drawing.ctx.getImageData(0, 0, this.editor.width, this.editor.height) + } + this.events.push(event) + } + pushAction(action) { + this.push({ + type: "action", + id: action + }) + } + editBegin(x, y) { + this.current_edit = { + type: "edit", + id: this.editor.getOptionValue("tool"), + options: Object.assign({}, this.editor.options), + points: [{ x: x, y: y }] + } + } + editMove(x, y) { + if (this.current_edit) { + this.current_edit.points.push({ x: x, y: y }) + } + } + editEnd(x, y) { + if (this.current_edit) { + this.push(this.current_edit) + this.current_edit = null + } + } + clear() { + this.events = [] + } + undo() { + this.rewindTo(this.rewind_index + 1) + } + redo() { + this.rewindTo(this.rewind_index - 1) + } + rewindTo(new_rewind_index) { + if (new_rewind_index < 0 || new_rewind_index > this.events.length) { + return // do nothing if target index is out of bounds + } - var ctx = this.editor.layers.drawing.ctx - ctx.clearRect(0, 0, this.editor.width, this.editor.height) + var ctx = this.editor.layers.drawing.ctx + ctx.clearRect(0, 0, this.editor.width, this.editor.height) - var target_index = this.events.length - 1 - new_rewind_index - var snapshot_index = target_index - while (snapshot_index > -1) { - if (this.events[snapshot_index].snapshot) { - break - } - snapshot_index-- - } + var target_index = this.events.length - 1 - new_rewind_index + var snapshot_index = target_index + while (snapshot_index > -1) { + if (this.events[snapshot_index].snapshot) { + break + } + snapshot_index-- + } - if (snapshot_index != -1) { - ctx.putImageData(this.events[snapshot_index].snapshot, 0, 0); - } + if (snapshot_index != -1) { + ctx.putImageData(this.events[snapshot_index].snapshot, 0, 0) + } - for (var i = (snapshot_index + 1); i <= target_index; i++) { - var event = this.events[i] - if (event.type == "action") { - var action = IMAGE_EDITOR_ACTIONS.find(a => a.id == event.id) - action.handler(this.editor) - } - else if (event.type == "edit") { - var tool = IMAGE_EDITOR_TOOLS.find(t => t.id == event.id) - this.editor.setBrush(this.editor.layers.drawing, event.options) + for (var i = snapshot_index + 1; i <= target_index; i++) { + var event = this.events[i] + if (event.type == "action") { + var action = IMAGE_EDITOR_ACTIONS.find((a) => a.id == event.id) + action.handler(this.editor) + } else if (event.type == "edit") { + var tool = IMAGE_EDITOR_TOOLS.find((t) => t.id == event.id) + this.editor.setBrush(this.editor.layers.drawing, event.options) - var first_point = event.points[0] - tool.begin(this.editor, ctx, first_point.x, first_point.y) - for (var point_i = 1; point_i < event.points.length; point_i++) { - tool.move(this.editor, ctx, event.points[point_i].x, event.points[point_i].y) - } - var last_point = event.points[event.points.length - 1] - tool.end(this.editor, ctx, last_point.x, last_point.y) - } - } + var first_point = event.points[0] + tool.begin(this.editor, ctx, first_point.x, first_point.y) + for (var point_i = 1; point_i < event.points.length; point_i++) { + tool.move(this.editor, ctx, event.points[point_i].x, event.points[point_i].y) + } + var last_point = event.points[event.points.length - 1] + tool.end(this.editor, ctx, last_point.x, last_point.y) + } + } - // re-set brush to current settings - this.editor.setBrush(this.editor.layers.drawing) + // re-set brush to current settings + this.editor.setBrush(this.editor.layers.drawing) - this.rewind_index = new_rewind_index - } + this.rewind_index = new_rewind_index + } } class ImageEditor { - constructor(popup, inpainter = false) { - this.inpainter = inpainter - this.popup = popup - this.history = new EditorHistory(this) - if (inpainter) { - this.popup.classList.add("inpainter") - } - this.drawing = false - this.temp_previous_tool = null // used for the ctrl-colorpicker functionality - this.container = popup.querySelector(".editor-controls-center > div") - this.layers = {} - var layer_names = [ - "background", - "drawing", - "overlay" - ] - layer_names.forEach(name => { - let canvas = document.createElement("canvas") - canvas.className = `editor-canvas-${name}` - this.container.appendChild(canvas) - this.layers[name] = { - name: name, - canvas: canvas, - ctx: canvas.getContext("2d") - } - }) + constructor(popup, inpainter = false) { + this.inpainter = inpainter + this.popup = popup + this.history = new EditorHistory(this) + if (inpainter) { + this.popup.classList.add("inpainter") + } + this.drawing = false + this.temp_previous_tool = null // used for the ctrl-colorpicker functionality + this.container = popup.querySelector(".editor-controls-center > div") + this.layers = {} + var layer_names = ["background", "drawing", "overlay"] + layer_names.forEach((name) => { + let canvas = document.createElement("canvas") + canvas.className = `editor-canvas-${name}` + this.container.appendChild(canvas) + this.layers[name] = { + name: name, + canvas: canvas, + ctx: canvas.getContext("2d") + } + }) - // add mouse handlers - this.container.addEventListener("mousedown", this.mouseHandler.bind(this)) - this.container.addEventListener("mouseup", this.mouseHandler.bind(this)) - this.container.addEventListener("mousemove", this.mouseHandler.bind(this)) - this.container.addEventListener("mouseout", this.mouseHandler.bind(this)) - this.container.addEventListener("mouseenter", this.mouseHandler.bind(this)) + // add mouse handlers + this.container.addEventListener("mousedown", this.mouseHandler.bind(this)) + this.container.addEventListener("mouseup", this.mouseHandler.bind(this)) + this.container.addEventListener("mousemove", this.mouseHandler.bind(this)) + this.container.addEventListener("mouseout", this.mouseHandler.bind(this)) + this.container.addEventListener("mouseenter", this.mouseHandler.bind(this)) - this.container.addEventListener("touchstart", this.mouseHandler.bind(this)) - this.container.addEventListener("touchmove", this.mouseHandler.bind(this)) - this.container.addEventListener("touchcancel", this.mouseHandler.bind(this)) - this.container.addEventListener("touchend", this.mouseHandler.bind(this)) + this.container.addEventListener("touchstart", this.mouseHandler.bind(this)) + this.container.addEventListener("touchmove", this.mouseHandler.bind(this)) + this.container.addEventListener("touchcancel", this.mouseHandler.bind(this)) + this.container.addEventListener("touchend", this.mouseHandler.bind(this)) - // initialize editor controls - this.options = {} - this.optionElements = {} - IMAGE_EDITOR_SECTIONS.forEach(section => { - section.id = `image_editor_${section.name}` - var sectionElement = document.createElement("div") - sectionElement.className = section.id - - var title = document.createElement("h4") - title.innerText = section.title - sectionElement.appendChild(title) - - var optionsContainer = document.createElement("div") - optionsContainer.classList.add("editor-options-container") - - this.optionElements[section.name] = [] - section.options.forEach((option, index) => { - var optionHolder = document.createElement("div") - var optionElement = document.createElement("div") - optionHolder.appendChild(optionElement) - section.initElement(optionElement, option) - optionElement.addEventListener("click", target => this.selectOption(section.name, index)) - optionsContainer.appendChild(optionHolder) - this.optionElements[section.name].push(optionElement) - }) - this.selectOption(section.name, section.options.indexOf(section.default)) - - sectionElement.appendChild(optionsContainer) - - this.popup.querySelector(".editor-controls-left").appendChild(sectionElement) - }) + // initialize editor controls + this.options = {} + this.optionElements = {} + IMAGE_EDITOR_SECTIONS.forEach((section) => { + section.id = `image_editor_${section.name}` + var sectionElement = document.createElement("div") + sectionElement.className = section.id - this.custom_color_input = this.popup.querySelector(`input[type="color"]`) - this.custom_color_input.addEventListener("change", () => { - this.custom_color_input.parentElement.style.background = this.custom_color_input.value - this.selectOption("color", 0) - }) + var title = document.createElement("h4") + title.innerText = section.title + sectionElement.appendChild(title) - if (this.inpainter) { - this.selectOption("color", IMAGE_EDITOR_SECTIONS.find(s => s.name == "color").options.indexOf("#ffffff")) - this.selectOption("opacity", IMAGE_EDITOR_SECTIONS.find(s => s.name == "opacity").options.indexOf(0.4)) - } + var optionsContainer = document.createElement("div") + optionsContainer.classList.add("editor-options-container") - // initialize the right-side controls - var buttonContainer = document.createElement("div") - IMAGE_EDITOR_BUTTONS.forEach(button => { - var element = document.createElement("div") - var icon = document.createElement("i") - element.className = "image-editor-button button" - icon.className = button.icon - element.appendChild(icon) - element.append(button.name) - buttonContainer.appendChild(element) - element.addEventListener("click", event => button.handler(this)) - }) - var actionsContainer = document.createElement("div") - var actionsTitle = document.createElement("h4") - actionsTitle.textContent = "Actions" - actionsContainer.appendChild(actionsTitle); - IMAGE_EDITOR_ACTIONS.forEach(action => { - var element = document.createElement("div") - var icon = document.createElement("i") - element.className = "image-editor-button button" - if (action.className) { - element.className += " " + action.className - } - icon.className = action.icon - element.appendChild(icon) - element.append(action.name) - actionsContainer.appendChild(element) - element.addEventListener("click", event => this.runAction(action.id)) - }) - this.popup.querySelector(".editor-controls-right").appendChild(actionsContainer) - this.popup.querySelector(".editor-controls-right").appendChild(buttonContainer) + this.optionElements[section.name] = [] + section.options.forEach((option, index) => { + var optionHolder = document.createElement("div") + var optionElement = document.createElement("div") + optionHolder.appendChild(optionElement) + section.initElement(optionElement, option) + optionElement.addEventListener("click", (target) => this.selectOption(section.name, index)) + optionsContainer.appendChild(optionHolder) + this.optionElements[section.name].push(optionElement) + }) + this.selectOption(section.name, section.options.indexOf(section.default)) - this.keyHandlerBound = this.keyHandler.bind(this) + sectionElement.appendChild(optionsContainer) - this.setSize(512, 512) - } - show() { - this.popup.classList.add("active") - document.addEventListener("keydown", this.keyHandlerBound, true) - document.addEventListener("keyup", this.keyHandlerBound, true) - } - hide() { - this.popup.classList.remove("active") - document.removeEventListener("keydown", this.keyHandlerBound, true) - document.removeEventListener("keyup", this.keyHandlerBound, true) - } - setSize(width, height) { - if (width == this.width && height == this.height) { - return - } + this.popup.querySelector(".editor-controls-left").appendChild(sectionElement) + }) - if (width > height) { - var max_size = Math.min(parseInt(window.innerWidth * 0.9), width, 768) - var multiplier = max_size / width - width = (multiplier * width).toFixed() - height = (multiplier * height).toFixed() - } - else { - var max_size = Math.min(parseInt(window.innerHeight * 0.9), height, 768) - var multiplier = max_size / height - width = (multiplier * width).toFixed() - height = (multiplier * height).toFixed() - } - this.width = parseInt(width) - this.height = parseInt(height) - - this.container.style.width = width + "px" - this.container.style.height = height + "px" - - Object.values(this.layers).forEach(layer => { - layer.canvas.width = width - layer.canvas.height = height - }) + this.custom_color_input = this.popup.querySelector(`input[type="color"]`) + this.custom_color_input.addEventListener("change", () => { + this.custom_color_input.parentElement.style.background = this.custom_color_input.value + this.selectOption("color", 0) + }) - if (this.inpainter) { - this.saveImage() // We've reset the size of the image so inpainting is different - } - this.setBrush() - this.history.clear() - } - get tool() { - var tool_id = this.getOptionValue("tool") - return IMAGE_EDITOR_TOOLS.find(t => t.id == tool_id); - } - loadTool() { - this.drawing = false - this.container.style.cursor = this.tool.cursor; - } - setImage(url, width, height) { - this.setSize(width, height) - this.layers.background.ctx.clearRect(0, 0, this.width, this.height) - if (!(url && this.inpainter)) { - this.layers.drawing.ctx.clearRect(0, 0, this.width, this.height) - } - if (url) { - var image = new Image() - image.onload = () => { - this.layers.background.ctx.drawImage(image, 0, 0, this.width, this.height) - } - image.src = url - } - else { - this.layers.background.ctx.fillStyle = "#ffffff" - this.layers.background.ctx.beginPath() - this.layers.background.ctx.rect(0, 0, this.width, this.height) - this.layers.background.ctx.fill() - } - this.history.clear() - } - saveImage() { - if (!this.inpainter) { - // This is not an inpainter, so save the image as the new img2img input - this.layers.background.ctx.drawImage(this.layers.drawing.canvas, 0, 0, this.width, this.height) - var base64 = this.layers.background.canvas.toDataURL() - initImagePreview.src = base64 // this will trigger the rest of the app to use it - } - else { - // This is an inpainter, so make sure the toggle is set accordingly - var is_blank = !this.layers.drawing.ctx - .getImageData(0, 0, this.width, this.height).data - .some(channel => channel !== 0) - maskSetting.checked = !is_blank - } - this.hide() - } - getImg() { // a drop-in replacement of the drawingboard version - return this.layers.drawing.canvas.toDataURL() - } - setImg(dataUrl) { // a drop-in replacement of the drawingboard version - var image = new Image() - image.onload = () => { - var ctx = this.layers.drawing.ctx; - ctx.clearRect(0, 0, this.width, this.height) - ctx.globalCompositeOperation = "source-over" - ctx.globalAlpha = 1 - ctx.filter = "none" - ctx.drawImage(image, 0, 0, this.width, this.height) - this.setBrush(this.layers.drawing) - } - image.src = dataUrl - } - runAction(action_id) { - var action = IMAGE_EDITOR_ACTIONS.find(a => a.id == action_id) - if (action.trackHistory) { - this.history.pushAction(action_id) - } - action.handler(this) - } - setBrush(layer = null, options = null) { - if (options == null) { - options = this.options - } - if (layer) { - layer.ctx.lineCap = "round" - layer.ctx.lineJoin = "round" - layer.ctx.lineWidth = options.brush_size - layer.ctx.fillStyle = options.color - layer.ctx.strokeStyle = options.color - var sharpness = parseInt(options.sharpness * options.brush_size) - layer.ctx.filter = sharpness == 0 ? `none` : `blur(${sharpness}px)` - layer.ctx.globalAlpha = (1 - options.opacity) - layer.ctx.globalCompositeOperation = "source-over" - var tool = IMAGE_EDITOR_TOOLS.find(t => t.id == options.tool) - if (tool && tool.setBrush) { - tool.setBrush(editor, layer) - } - } - else { - Object.values([ "drawing", "overlay" ]).map(name => this.layers[name]).forEach(l => { - this.setBrush(l) - }) - } - } - get ctx_overlay() { - return this.layers.overlay.ctx - } - get ctx_current() { // the idea is this will help support having custom layers and editing each one - return this.layers.drawing.ctx - } - get canvas_current() { - return this.layers.drawing.canvas - } - keyHandler(event) { // handles keybinds like ctrl+z, ctrl+y - if (!this.popup.classList.contains("active")) { - document.removeEventListener("keydown", this.keyHandlerBound) - document.removeEventListener("keyup", this.keyHandlerBound) - return // this catches if something else closes the window but doesnt properly unbind the key handler - } + if (this.inpainter) { + this.selectOption("color", IMAGE_EDITOR_SECTIONS.find((s) => s.name == "color").options.indexOf("#ffffff")) + this.selectOption("opacity", IMAGE_EDITOR_SECTIONS.find((s) => s.name == "opacity").options.indexOf(0.4)) + } - // keybindings - if (event.type == "keydown") { - if ((event.key == "z" || event.key == "Z") && event.ctrlKey) { - if (!event.shiftKey) { - this.history.undo() - } - else { - this.history.redo() - } - event.stopPropagation(); - event.preventDefault(); - } - if (event.key == "y" && event.ctrlKey) { - this.history.redo() - event.stopPropagation(); - event.preventDefault(); - } - if (event.key === "Escape") { - this.hide() - event.stopPropagation(); - event.preventDefault(); - } - } - - // dropper ctrl holding handler stuff - var dropper_active = this.temp_previous_tool != null; - if (dropper_active && !event.ctrlKey) { - this.selectOption("tool", IMAGE_EDITOR_TOOLS.findIndex(t => t.id == this.temp_previous_tool)) - this.temp_previous_tool = null - } - else if (!dropper_active && event.ctrlKey) { - this.temp_previous_tool = this.getOptionValue("tool") - this.selectOption("tool", IMAGE_EDITOR_TOOLS.findIndex(t => t.id == "colorpicker")) - } - } - mouseHandler(event) { - var bbox = this.layers.overlay.canvas.getBoundingClientRect() - var x = (event.clientX || 0) - bbox.left - var y = (event.clientY || 0) - bbox.top - var type = event.type; - var touchmap = { - touchstart: "mousedown", - touchmove: "mousemove", - touchend: "mouseup", - touchcancel: "mouseup" - } - if (type in touchmap) { - type = touchmap[type] - if (event.touches && event.touches[0]) { - var touch = event.touches[0] - var x = (touch.clientX || 0) - bbox.left - var y = (touch.clientY || 0) - bbox.top - } - } - event.preventDefault() - // do drawing-related stuff - if (type == "mousedown" || (type == "mouseenter" && event.buttons == 1)) { - this.drawing = true - this.tool.begin(this, this.ctx_current, x, y) - this.tool.begin(this, this.ctx_overlay, x, y, true) - this.history.editBegin(x, y) - } - if (type == "mouseup" || type == "mousemove") { - if (this.drawing) { - if (x > 0 && y > 0) { - this.tool.move(this, this.ctx_current, x, y) - this.tool.move(this, this.ctx_overlay, x, y, true) - this.history.editMove(x, y) - } - } - } - if (type == "mouseup" || type == "mouseout") { - if (this.drawing) { - this.drawing = false - this.tool.end(this, this.ctx_current, x, y) - this.tool.end(this, this.ctx_overlay, x, y, true) - this.history.editEnd(x, y) - } - } - } - getOptionValue(section_name) { - var section = IMAGE_EDITOR_SECTIONS.find(s => s.name == section_name) - return this.options && section_name in this.options ? this.options[section_name] : section.default - } - selectOption(section_name, option_index) { - var section = IMAGE_EDITOR_SECTIONS.find(s => s.name == section_name) - var value = section.options[option_index] - this.options[section_name] = value == "custom" ? section.getCustom(this) : value - - this.optionElements[section_name].forEach(element => element.classList.remove("active")) - this.optionElements[section_name][option_index].classList.add("active") - - // change the editor - this.setBrush() - if (section.name == "tool") { - this.loadTool() - } - } + // initialize the right-side controls + var buttonContainer = document.createElement("div") + IMAGE_EDITOR_BUTTONS.forEach((button) => { + var element = document.createElement("div") + var icon = document.createElement("i") + element.className = "image-editor-button button" + icon.className = button.icon + element.appendChild(icon) + element.append(button.name) + buttonContainer.appendChild(element) + element.addEventListener("click", (event) => button.handler(this)) + }) + var actionsContainer = document.createElement("div") + var actionsTitle = document.createElement("h4") + actionsTitle.textContent = "Actions" + actionsContainer.appendChild(actionsTitle) + IMAGE_EDITOR_ACTIONS.forEach((action) => { + var element = document.createElement("div") + var icon = document.createElement("i") + element.className = "image-editor-button button" + if (action.className) { + element.className += " " + action.className + } + icon.className = action.icon + element.appendChild(icon) + element.append(action.name) + actionsContainer.appendChild(element) + element.addEventListener("click", (event) => this.runAction(action.id)) + }) + this.popup.querySelector(".editor-controls-right").appendChild(actionsContainer) + this.popup.querySelector(".editor-controls-right").appendChild(buttonContainer) + + this.keyHandlerBound = this.keyHandler.bind(this) + + this.setSize(512, 512) + } + show() { + this.popup.classList.add("active") + document.addEventListener("keydown", this.keyHandlerBound, true) + document.addEventListener("keyup", this.keyHandlerBound, true) + } + hide() { + this.popup.classList.remove("active") + document.removeEventListener("keydown", this.keyHandlerBound, true) + document.removeEventListener("keyup", this.keyHandlerBound, true) + } + setSize(width, height) { + if (width == this.width && height == this.height) { + return + } + + if (width > height) { + var max_size = Math.min(parseInt(window.innerWidth * 0.9), width, 768) + var multiplier = max_size / width + width = (multiplier * width).toFixed() + height = (multiplier * height).toFixed() + } else { + var max_size = Math.min(parseInt(window.innerHeight * 0.9), height, 768) + var multiplier = max_size / height + width = (multiplier * width).toFixed() + height = (multiplier * height).toFixed() + } + this.width = parseInt(width) + this.height = parseInt(height) + + this.container.style.width = width + "px" + this.container.style.height = height + "px" + + Object.values(this.layers).forEach((layer) => { + layer.canvas.width = width + layer.canvas.height = height + }) + + if (this.inpainter) { + this.saveImage() // We've reset the size of the image so inpainting is different + } + this.setBrush() + this.history.clear() + } + get tool() { + var tool_id = this.getOptionValue("tool") + return IMAGE_EDITOR_TOOLS.find((t) => t.id == tool_id) + } + loadTool() { + this.drawing = false + this.container.style.cursor = this.tool.cursor + } + setImage(url, width, height) { + this.setSize(width, height) + this.layers.background.ctx.clearRect(0, 0, this.width, this.height) + if (!(url && this.inpainter)) { + this.layers.drawing.ctx.clearRect(0, 0, this.width, this.height) + } + if (url) { + var image = new Image() + image.onload = () => { + this.layers.background.ctx.drawImage(image, 0, 0, this.width, this.height) + } + image.src = url + } else { + this.layers.background.ctx.fillStyle = "#ffffff" + this.layers.background.ctx.beginPath() + this.layers.background.ctx.rect(0, 0, this.width, this.height) + this.layers.background.ctx.fill() + } + this.history.clear() + } + saveImage() { + if (!this.inpainter) { + // This is not an inpainter, so save the image as the new img2img input + this.layers.background.ctx.drawImage(this.layers.drawing.canvas, 0, 0, this.width, this.height) + var base64 = this.layers.background.canvas.toDataURL() + initImagePreview.src = base64 // this will trigger the rest of the app to use it + } else { + // This is an inpainter, so make sure the toggle is set accordingly + var is_blank = !this.layers.drawing.ctx + .getImageData(0, 0, this.width, this.height) + .data.some((channel) => channel !== 0) + maskSetting.checked = !is_blank + } + this.hide() + } + getImg() { + // a drop-in replacement of the drawingboard version + return this.layers.drawing.canvas.toDataURL() + } + setImg(dataUrl) { + // a drop-in replacement of the drawingboard version + var image = new Image() + image.onload = () => { + var ctx = this.layers.drawing.ctx + ctx.clearRect(0, 0, this.width, this.height) + ctx.globalCompositeOperation = "source-over" + ctx.globalAlpha = 1 + ctx.filter = "none" + ctx.drawImage(image, 0, 0, this.width, this.height) + this.setBrush(this.layers.drawing) + } + image.src = dataUrl + } + runAction(action_id) { + var action = IMAGE_EDITOR_ACTIONS.find((a) => a.id == action_id) + if (action.trackHistory) { + this.history.pushAction(action_id) + } + action.handler(this) + } + setBrush(layer = null, options = null) { + if (options == null) { + options = this.options + } + if (layer) { + layer.ctx.lineCap = "round" + layer.ctx.lineJoin = "round" + layer.ctx.lineWidth = options.brush_size + layer.ctx.fillStyle = options.color + layer.ctx.strokeStyle = options.color + var sharpness = parseInt(options.sharpness * options.brush_size) + layer.ctx.filter = sharpness == 0 ? `none` : `blur(${sharpness}px)` + layer.ctx.globalAlpha = 1 - options.opacity + layer.ctx.globalCompositeOperation = "source-over" + var tool = IMAGE_EDITOR_TOOLS.find((t) => t.id == options.tool) + if (tool && tool.setBrush) { + tool.setBrush(editor, layer) + } + } else { + Object.values(["drawing", "overlay"]) + .map((name) => this.layers[name]) + .forEach((l) => { + this.setBrush(l) + }) + } + } + get ctx_overlay() { + return this.layers.overlay.ctx + } + get ctx_current() { + // the idea is this will help support having custom layers and editing each one + return this.layers.drawing.ctx + } + get canvas_current() { + return this.layers.drawing.canvas + } + keyHandler(event) { + // handles keybinds like ctrl+z, ctrl+y + if (!this.popup.classList.contains("active")) { + document.removeEventListener("keydown", this.keyHandlerBound) + document.removeEventListener("keyup", this.keyHandlerBound) + return // this catches if something else closes the window but doesnt properly unbind the key handler + } + + // keybindings + if (event.type == "keydown") { + if ((event.key == "z" || event.key == "Z") && event.ctrlKey) { + if (!event.shiftKey) { + this.history.undo() + } else { + this.history.redo() + } + event.stopPropagation() + event.preventDefault() + } + if (event.key == "y" && event.ctrlKey) { + this.history.redo() + event.stopPropagation() + event.preventDefault() + } + if (event.key === "Escape") { + this.hide() + event.stopPropagation() + event.preventDefault() + } + } + + // dropper ctrl holding handler stuff + var dropper_active = this.temp_previous_tool != null + if (dropper_active && !event.ctrlKey) { + this.selectOption( + "tool", + IMAGE_EDITOR_TOOLS.findIndex((t) => t.id == this.temp_previous_tool) + ) + this.temp_previous_tool = null + } else if (!dropper_active && event.ctrlKey) { + this.temp_previous_tool = this.getOptionValue("tool") + this.selectOption( + "tool", + IMAGE_EDITOR_TOOLS.findIndex((t) => t.id == "colorpicker") + ) + } + } + mouseHandler(event) { + var bbox = this.layers.overlay.canvas.getBoundingClientRect() + var x = (event.clientX || 0) - bbox.left + var y = (event.clientY || 0) - bbox.top + var type = event.type + var touchmap = { + touchstart: "mousedown", + touchmove: "mousemove", + touchend: "mouseup", + touchcancel: "mouseup" + } + if (type in touchmap) { + type = touchmap[type] + if (event.touches && event.touches[0]) { + var touch = event.touches[0] + var x = (touch.clientX || 0) - bbox.left + var y = (touch.clientY || 0) - bbox.top + } + } + event.preventDefault() + // do drawing-related stuff + if (type == "mousedown" || (type == "mouseenter" && event.buttons == 1)) { + this.drawing = true + this.tool.begin(this, this.ctx_current, x, y) + this.tool.begin(this, this.ctx_overlay, x, y, true) + this.history.editBegin(x, y) + } + if (type == "mouseup" || type == "mousemove") { + if (this.drawing) { + if (x > 0 && y > 0) { + this.tool.move(this, this.ctx_current, x, y) + this.tool.move(this, this.ctx_overlay, x, y, true) + this.history.editMove(x, y) + } + } + } + if (type == "mouseup" || type == "mouseout") { + if (this.drawing) { + this.drawing = false + this.tool.end(this, this.ctx_current, x, y) + this.tool.end(this, this.ctx_overlay, x, y, true) + this.history.editEnd(x, y) + } + } + } + getOptionValue(section_name) { + var section = IMAGE_EDITOR_SECTIONS.find((s) => s.name == section_name) + return this.options && section_name in this.options ? this.options[section_name] : section.default + } + selectOption(section_name, option_index) { + var section = IMAGE_EDITOR_SECTIONS.find((s) => s.name == section_name) + var value = section.options[option_index] + this.options[section_name] = value == "custom" ? section.getCustom(this) : value + + this.optionElements[section_name].forEach((element) => element.classList.remove("active")) + this.optionElements[section_name][option_index].classList.add("active") + + // change the editor + this.setBrush() + if (section.name == "tool") { + this.loadTool() + } + } } const imageEditor = new ImageEditor(document.getElementById("image-editor")) @@ -770,114 +802,126 @@ imageEditor.setImage(null, 512, 512) imageInpainter.setImage(null, 512, 512) document.getElementById("init_image_button_draw").addEventListener("click", () => { - imageEditor.show() + imageEditor.show() }) document.getElementById("init_image_button_inpaint").addEventListener("click", () => { - imageInpainter.show() + imageInpainter.show() }) img2imgUnload() // no init image when the app starts - function rgbToHex(rgb) { - function componentToHex(c) { - var hex = parseInt(c).toString(16) - return hex.length == 1 ? "0" + hex : hex - } - return "#" + componentToHex(rgb.r) + componentToHex(rgb.g) + componentToHex(rgb.b) + function componentToHex(c) { + var hex = parseInt(c).toString(16) + return hex.length == 1 ? "0" + hex : hex + } + return "#" + componentToHex(rgb.r) + componentToHex(rgb.g) + componentToHex(rgb.b) } function hexToRgb(hex) { - var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16) - } : null; + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) + return result + ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } + : null } function pixelCompare(int1, int2) { - return Math.abs(int1 - int2) < 4 + return Math.abs(int1 - int2) < 4 } // adapted from https://ben.akrin.com/canvas_fill/fill_04.html function flood_fill(editor, the_canvas_context, x, y, color) { - pixel_stack = [{x:x, y:y}] ; - pixels = the_canvas_context.getImageData( 0, 0, editor.width, editor.height ) ; - var linear_cords = ( y * editor.width + x ) * 4 ; - var original_color = {r:pixels.data[linear_cords], - g:pixels.data[linear_cords+1], - b:pixels.data[linear_cords+2], - a:pixels.data[linear_cords+3]} ; - - var opacity = color.a / 255; - var new_color = { - r: parseInt((color.r * opacity) + (original_color.r * (1 - opacity))), - g: parseInt((color.g * opacity) + (original_color.g * (1 - opacity))), - b: parseInt((color.b * opacity) + (original_color.b * (1 - opacity))) - } + pixel_stack = [{ x: x, y: y }] + pixels = the_canvas_context.getImageData(0, 0, editor.width, editor.height) + var linear_cords = (y * editor.width + x) * 4 + var original_color = { + r: pixels.data[linear_cords], + g: pixels.data[linear_cords + 1], + b: pixels.data[linear_cords + 2], + a: pixels.data[linear_cords + 3] + } - if ((pixelCompare(new_color.r, original_color.r) && - pixelCompare(new_color.g, original_color.g) && - pixelCompare(new_color.b, original_color.b))) - { - return; // This color is already the color we want, so do nothing - } - var max_stack_size = editor.width * editor.height; - while( pixel_stack.length > 0 && pixel_stack.length < max_stack_size ) { - new_pixel = pixel_stack.shift() ; - x = new_pixel.x ; - y = new_pixel.y ; - - linear_cords = ( y * editor.width + x ) * 4 ; - while( y-->=0 && - (pixelCompare(pixels.data[linear_cords], original_color.r) && - pixelCompare(pixels.data[linear_cords+1], original_color.g) && - pixelCompare(pixels.data[linear_cords+2], original_color.b))) { - linear_cords -= editor.width * 4 ; - } - linear_cords += editor.width * 4 ; - y++ ; + var opacity = color.a / 255 + var new_color = { + r: parseInt(color.r * opacity + original_color.r * (1 - opacity)), + g: parseInt(color.g * opacity + original_color.g * (1 - opacity)), + b: parseInt(color.b * opacity + original_color.b * (1 - opacity)) + } - var reached_left = false ; - var reached_right = false ; - while( y++ 0 && pixel_stack.length < max_stack_size) { + new_pixel = pixel_stack.shift() + x = new_pixel.x + y = new_pixel.y - if( x>0 ) { - if( pixelCompare(pixels.data[linear_cords-4], original_color.r) && - pixelCompare(pixels.data[linear_cords-4+1], original_color.g) && - pixelCompare(pixels.data[linear_cords-4+2], original_color.b)) { - if( !reached_left ) { - pixel_stack.push( {x:x-1, y:y} ) ; - reached_left = true ; - } - } else if( reached_left ) { - reached_left = false ; - } - } - - if( x= 0 && + pixelCompare(pixels.data[linear_cords], original_color.r) && + pixelCompare(pixels.data[linear_cords + 1], original_color.g) && + pixelCompare(pixels.data[linear_cords + 2], original_color.b) + ) { + linear_cords -= editor.width * 4 + } + linear_cords += editor.width * 4 + y++ + + var reached_left = false + var reached_right = false + while ( + y++ < editor.height && + pixelCompare(pixels.data[linear_cords], original_color.r) && + pixelCompare(pixels.data[linear_cords + 1], original_color.g) && + pixelCompare(pixels.data[linear_cords + 2], original_color.b) + ) { + pixels.data[linear_cords] = new_color.r + pixels.data[linear_cords + 1] = new_color.g + pixels.data[linear_cords + 2] = new_color.b + pixels.data[linear_cords + 3] = 255 + + if (x > 0) { + if ( + pixelCompare(pixels.data[linear_cords - 4], original_color.r) && + pixelCompare(pixels.data[linear_cords - 4 + 1], original_color.g) && + pixelCompare(pixels.data[linear_cords - 4 + 2], original_color.b) + ) { + if (!reached_left) { + pixel_stack.push({ x: x - 1, y: y }) + reached_left = true + } + } else if (reached_left) { + reached_left = false + } + } + + if (x < editor.width - 1) { + if ( + pixelCompare(pixels.data[linear_cords + 4], original_color.r) && + pixelCompare(pixels.data[linear_cords + 4 + 1], original_color.g) && + pixelCompare(pixels.data[linear_cords + 4 + 2], original_color.b) + ) { + if (!reached_right) { + pixel_stack.push({ x: x + 1, y: y }) + reached_right = true + } + } else if (reached_right) { + reached_right = false + } + } + + linear_cords += editor.width * 4 + } + } + the_canvas_context.putImageData(pixels, 0, 0) } diff --git a/ui/media/js/image-modal.js b/ui/media/js/image-modal.js index 367c754c..df255756 100644 --- a/ui/media/js/image-modal.js +++ b/ui/media/js/image-modal.js @@ -11,56 +11,35 @@ * @type {(() => (string | ImageModalRequest) | string | ImageModalRequest) => {}} */ const imageModal = (function() { - const backElem = createElement( - 'i', - undefined, - ['fa-solid', 'fa-arrow-left', 'tertiaryButton'], - ) + const backElem = createElement("i", undefined, ["fa-solid", "fa-arrow-left", "tertiaryButton"]) - const forwardElem = createElement( - 'i', - undefined, - ['fa-solid', 'fa-arrow-right', 'tertiaryButton'], - ) + const forwardElem = createElement("i", undefined, ["fa-solid", "fa-arrow-right", "tertiaryButton"]) - const zoomElem = createElement( - 'i', - undefined, - ['fa-solid', 'tertiaryButton'], - ) + const zoomElem = createElement("i", undefined, ["fa-solid", "tertiaryButton"]) - const closeElem = createElement( - 'i', - undefined, - ['fa-solid', 'fa-xmark', 'tertiaryButton'], - ) + const closeElem = createElement("i", undefined, ["fa-solid", "fa-xmark", "tertiaryButton"]) - const menuBarElem = createElement('div', undefined, 'menu-bar', [backElem, forwardElem, zoomElem, closeElem]) + const menuBarElem = createElement("div", undefined, "menu-bar", [backElem, forwardElem, zoomElem, closeElem]) - const imageContainer = createElement('div', undefined, 'image-wrapper') + const imageContainer = createElement("div", undefined, "image-wrapper") - const backdrop = createElement('div', undefined, 'backdrop') + const backdrop = createElement("div", undefined, "backdrop") - const modalContainer = createElement('div', undefined, 'content', [menuBarElem, imageContainer]) + const modalContainer = createElement("div", undefined, "content", [menuBarElem, imageContainer]) - const modalElem = createElement( - 'div', - { id: 'viewFullSizeImgModal' }, - ['popup'], - [backdrop, modalContainer], - ) + const modalElem = createElement("div", { id: "viewFullSizeImgModal" }, ["popup"], [backdrop, modalContainer]) document.body.appendChild(modalElem) const setZoomLevel = (value) => { - const img = imageContainer.querySelector('img') + const img = imageContainer.querySelector("img") if (value) { - zoomElem.classList.remove('fa-magnifying-glass-plus') - zoomElem.classList.add('fa-magnifying-glass-minus') + zoomElem.classList.remove("fa-magnifying-glass-plus") + zoomElem.classList.add("fa-magnifying-glass-minus") if (img) { - img.classList.remove('natural-zoom') + img.classList.remove("natural-zoom") - let zoomLevel = typeof value === 'number' ? value : img.dataset.zoomLevel + let zoomLevel = typeof value === "number" ? value : img.dataset.zoomLevel if (!zoomLevel) { zoomLevel = 100 } @@ -70,36 +49,35 @@ const imageModal = (function() { img.height = img.naturalHeight * (+zoomLevel / 100) } } else { - zoomElem.classList.remove('fa-magnifying-glass-minus') - zoomElem.classList.add('fa-magnifying-glass-plus') + zoomElem.classList.remove("fa-magnifying-glass-minus") + zoomElem.classList.add("fa-magnifying-glass-plus") if (img) { - img.classList.add('natural-zoom') - img.removeAttribute('width') - img.removeAttribute('height') + img.classList.add("natural-zoom") + img.removeAttribute("width") + img.removeAttribute("height") } } } - zoomElem.addEventListener( - 'click', - () => setZoomLevel(imageContainer.querySelector('img')?.classList?.contains('natural-zoom')), + zoomElem.addEventListener("click", () => + setZoomLevel(imageContainer.querySelector("img")?.classList?.contains("natural-zoom")) ) const state = { previous: undefined, - next: undefined, + next: undefined } const clear = () => { - imageContainer.innerHTML = '' + imageContainer.innerHTML = "" - Object.keys(state).forEach(key => delete state[key]) + Object.keys(state).forEach((key) => delete state[key]) } const close = () => { clear() - modalElem.classList.remove('active') - document.body.style.overflow = 'initial' + modalElem.classList.remove("active") + document.body.style.overflow = "initial" } /** @@ -113,27 +91,27 @@ const imageModal = (function() { clear() - const options = typeof optionsFactory === 'function' ? optionsFactory() : optionsFactory - const src = typeof options === 'string' ? options : options.src + const options = typeof optionsFactory === "function" ? optionsFactory() : optionsFactory + const src = typeof options === "string" ? options : options.src - const imgElem = createElement('img', { src }, 'natural-zoom') + const imgElem = createElement("img", { src }, "natural-zoom") imageContainer.appendChild(imgElem) - modalElem.classList.add('active') - document.body.style.overflow = 'hidden' + modalElem.classList.add("active") + document.body.style.overflow = "hidden" setZoomLevel(false) - if (typeof options === 'object' && options.previous) { + if (typeof options === "object" && options.previous) { state.previous = options.previous - backElem.style.display = 'unset' + backElem.style.display = "unset" } else { - backElem.style.display = 'none' + backElem.style.display = "none" } - if (typeof options === 'object' && options.next) { + if (typeof options === "object" && options.next) { state.next = options.next - forwardElem.style.display = 'unset' + forwardElem.style.display = "unset" } else { - forwardElem.style.display = 'none' + forwardElem.style.display = "none" } } @@ -141,7 +119,7 @@ const imageModal = (function() { if (state.previous) { init(state.previous) } else { - backElem.style.display = 'none' + backElem.style.display = "none" } } @@ -149,27 +127,27 @@ const imageModal = (function() { if (state.next) { init(state.next) } else { - forwardElem.style.display = 'none' + forwardElem.style.display = "none" } } - window.addEventListener('keydown', (e) => { - if (modalElem.classList.contains('active')) { + window.addEventListener("keydown", (e) => { + if (modalElem.classList.contains("active")) { switch (e.key) { - case 'Escape': + case "Escape": close() break - case 'ArrowLeft': + case "ArrowLeft": back() break - case 'ArrowRight': + case "ArrowRight": forward() break } } }) - window.addEventListener('click', (e) => { - if (modalElem.classList.contains('active')) { + window.addEventListener("click", (e) => { + if (modalElem.classList.contains("active")) { if (e.target === backdrop || e.target === closeElem) { close() } @@ -180,9 +158,9 @@ const imageModal = (function() { } }) - backElem.addEventListener('click', back) + backElem.addEventListener("click", back) - forwardElem.addEventListener('click', forward) + forwardElem.addEventListener("click", forward) /** * @param {() => (string | ImageModalRequest) | string | ImageModalRequest} optionsFactory diff --git a/ui/media/js/image-modifiers.js b/ui/media/js/image-modifiers.js index eaea07f6..0671a7c7 100644 --- a/ui/media/js/image-modifiers.js +++ b/ui/media/js/image-modifiers.js @@ -3,26 +3,26 @@ let modifiers = [] let customModifiersGroupElement = undefined let customModifiersInitialContent -let editorModifierEntries = document.querySelector('#editor-modifiers-entries') -let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list') -let editorTagsContainer = document.querySelector('#editor-inputs-tags-container') -let modifierCardSizeSlider = document.querySelector('#modifier-card-size-slider') -let previewImageField = document.querySelector('#preview-image') -let modifierSettingsBtn = document.querySelector('#modifier-settings-btn') -let modifierSettingsOverlay = document.querySelector('#modifier-settings-config') -let customModifiersTextBox = document.querySelector('#custom-modifiers-input') -let customModifierEntriesToolbar = document.querySelector('#editor-modifiers-entries-toolbar') +let editorModifierEntries = document.querySelector("#editor-modifiers-entries") +let editorModifierTagsList = document.querySelector("#editor-inputs-tags-list") +let editorTagsContainer = document.querySelector("#editor-inputs-tags-container") +let modifierCardSizeSlider = document.querySelector("#modifier-card-size-slider") +let previewImageField = document.querySelector("#preview-image") +let modifierSettingsBtn = document.querySelector("#modifier-settings-btn") +let modifierSettingsOverlay = document.querySelector("#modifier-settings-config") +let customModifiersTextBox = document.querySelector("#custom-modifiers-input") +let customModifierEntriesToolbar = document.querySelector("#editor-modifiers-entries-toolbar") -const modifierThumbnailPath = 'media/modifier-thumbnails' -const activeCardClass = 'modifier-card-active' +const modifierThumbnailPath = "media/modifier-thumbnails" +const activeCardClass = "modifier-card-active" const CUSTOM_MODIFIERS_KEY = "customModifiers" function createModifierCard(name, previews, removeBy) { - const modifierCard = document.createElement('div') + const modifierCard = document.createElement("div") let style = previewImageField.value - let styleIndex = (style=='portrait') ? 0 : 1 + let styleIndex = style == "portrait" ? 0 : 1 - modifierCard.className = 'modifier-card' + modifierCard.className = "modifier-card" modifierCard.innerHTML = `
@@ -34,35 +34,35 @@ function createModifierCard(name, previews, removeBy) {

` - const image = modifierCard.querySelector('.modifier-card-image') - const errorText = modifierCard.querySelector('.modifier-card-error-label') - const label = modifierCard.querySelector('.modifier-card-label') + const image = modifierCard.querySelector(".modifier-card-image") + const errorText = modifierCard.querySelector(".modifier-card-error-label") + const label = modifierCard.querySelector(".modifier-card-label") - errorText.innerText = 'No Image' + errorText.innerText = "No Image" - if (typeof previews == 'object') { - image.src = previews[styleIndex]; // portrait - image.setAttribute('preview-type', style) + if (typeof previews == "object") { + image.src = previews[styleIndex] // portrait + image.setAttribute("preview-type", style) } else { image.remove() } const maxLabelLength = 30 - const cardLabel = removeBy ? name.replace('by ', '') : name + const cardLabel = removeBy ? name.replace("by ", "") : name - if(cardLabel.length <= maxLabelLength) { - label.querySelector('p').innerText = cardLabel + if (cardLabel.length <= maxLabelLength) { + label.querySelector("p").innerText = cardLabel } else { - const tooltipText = document.createElement('span') - tooltipText.className = 'tooltip-text' + const tooltipText = document.createElement("span") + tooltipText.className = "tooltip-text" tooltipText.innerText = name - label.classList.add('tooltip') + label.classList.add("tooltip") label.appendChild(tooltipText) - label.querySelector('p').innerText = cardLabel.substring(0, maxLabelLength) + '...' + label.querySelector("p").innerText = cardLabel.substring(0, maxLabelLength) + "..." } - label.querySelector('p').dataset.fullName = name // preserve the full name + label.querySelector("p").dataset.fullName = name // preserve the full name return modifierCard } @@ -71,55 +71,58 @@ function createModifierGroup(modifierGroup, initiallyExpanded, removeBy) { const title = modifierGroup.category const modifiers = modifierGroup.modifiers - const titleEl = document.createElement('h5') - titleEl.className = 'collapsible' + const titleEl = document.createElement("h5") + titleEl.className = "collapsible" titleEl.innerText = title - const modifiersEl = document.createElement('div') - modifiersEl.classList.add('collapsible-content', 'editor-modifiers-leaf') + const modifiersEl = document.createElement("div") + modifiersEl.classList.add("collapsible-content", "editor-modifiers-leaf") if (initiallyExpanded === true) { - titleEl.className += ' active' + titleEl.className += " active" } - modifiers.forEach(modObj => { + modifiers.forEach((modObj) => { const modifierName = modObj.modifier - const modifierPreviews = modObj?.previews?.map(preview => `${IMAGE_REGEX.test(preview.image) ? preview.image : modifierThumbnailPath + '/' + preview.path}`) + const modifierPreviews = modObj?.previews?.map( + (preview) => + `${IMAGE_REGEX.test(preview.image) ? preview.image : modifierThumbnailPath + "/" + preview.path}` + ) const modifierCard = createModifierCard(modifierName, modifierPreviews, removeBy) - if(typeof modifierCard == 'object') { + if (typeof modifierCard == "object") { modifiersEl.appendChild(modifierCard) const trimmedName = trimModifiers(modifierName) - modifierCard.addEventListener('click', () => { - if (activeTags.map(x => trimModifiers(x.name)).includes(trimmedName)) { + modifierCard.addEventListener("click", () => { + if (activeTags.map((x) => trimModifiers(x.name)).includes(trimmedName)) { // remove modifier from active array - activeTags = activeTags.filter(x => trimModifiers(x.name) != trimmedName) + activeTags = activeTags.filter((x) => trimModifiers(x.name) != trimmedName) toggleCardState(trimmedName, false) } else { // add modifier to active array activeTags.push({ - 'name': modifierName, - 'element': modifierCard.cloneNode(true), - 'originElement': modifierCard, - 'previews': modifierPreviews + name: modifierName, + element: modifierCard.cloneNode(true), + originElement: modifierCard, + previews: modifierPreviews }) toggleCardState(trimmedName, true) } refreshTagsList() - document.dispatchEvent(new Event('refreshImageModifiers')) + document.dispatchEvent(new Event("refreshImageModifiers")) }) } }) - let brk = document.createElement('br') - brk.style.clear = 'both' + let brk = document.createElement("br") + brk.style.clear = "both" modifiersEl.appendChild(brk) - let e = document.createElement('div') - e.className = 'modifier-category' + let e = document.createElement("div") + e.className = "modifier-category" e.appendChild(titleEl) e.appendChild(modifiersEl) @@ -130,87 +133,98 @@ function createModifierGroup(modifierGroup, initiallyExpanded, removeBy) { function trimModifiers(tag) { // Remove trailing '-' and/or '+' - tag = tag.replace(/[-+]+$/, ''); + tag = tag.replace(/[-+]+$/, "") // Remove parentheses at beginning and end - return tag.replace(/^[(]+|[\s)]+$/g, ''); + return tag.replace(/^[(]+|[\s)]+$/g, "") } async function loadModifiers() { try { - let res = await fetch('/get/modifiers') + let res = await fetch("/get/modifiers") if (res.status === 200) { res = await res.json() - modifiers = res; // update global variable + modifiers = res // update global variable res.reverse() res.forEach((modifierGroup, idx) => { - createModifierGroup(modifierGroup, idx === res.length - 1, modifierGroup === 'Artist' ? true : false) // only remove "By " for artists + createModifierGroup(modifierGroup, idx === res.length - 1, modifierGroup === "Artist" ? true : false) // only remove "By " for artists }) createCollapsibles(editorModifierEntries) } } catch (e) { - console.error('error fetching modifiers', e) + console.error("error fetching modifiers", e) } loadCustomModifiers() resizeModifierCards(modifierCardSizeSlider.value) - document.dispatchEvent(new Event('loadImageModifiers')) + document.dispatchEvent(new Event("loadImageModifiers")) } function refreshModifiersState(newTags, inactiveTags) { // clear existing modifiers - document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(modifierCard => { - const modifierName = modifierCard.querySelector('.modifier-card-label p').dataset.fullName // pick the full modifier name - if (activeTags.map(x => x.name).includes(modifierName)) { - modifierCard.classList.remove(activeCardClass) - modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+' - } - }) + document + .querySelector("#editor-modifiers") + .querySelectorAll(".modifier-card") + .forEach((modifierCard) => { + const modifierName = modifierCard.querySelector(".modifier-card-label p").dataset.fullName // pick the full modifier name + if (activeTags.map((x) => x.name).includes(modifierName)) { + modifierCard.classList.remove(activeCardClass) + modifierCard.querySelector(".modifier-card-image-overlay").innerText = "+" + } + }) activeTags = [] // set new modifiers - newTags.forEach(tag => { + newTags.forEach((tag) => { let found = false - document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(modifierCard => { - const modifierName = modifierCard.querySelector('.modifier-card-label p').dataset.fullName - const shortModifierName = modifierCard.querySelector('.modifier-card-label p').innerText - if (trimModifiers(tag) == trimModifiers(modifierName)) { - // add modifier to active array - if (!activeTags.map(x => x.name).includes(tag)) { // only add each tag once even if several custom modifier cards share the same tag - const imageModifierCard = modifierCard.cloneNode(true) - imageModifierCard.querySelector('.modifier-card-label p').innerText = tag.replace(modifierName, shortModifierName) - activeTags.push({ - 'name': tag, - 'element': imageModifierCard, - 'originElement': modifierCard - }) + document + .querySelector("#editor-modifiers") + .querySelectorAll(".modifier-card") + .forEach((modifierCard) => { + const modifierName = modifierCard.querySelector(".modifier-card-label p").dataset.fullName + const shortModifierName = modifierCard.querySelector(".modifier-card-label p").innerText + if (trimModifiers(tag) == trimModifiers(modifierName)) { + // add modifier to active array + if (!activeTags.map((x) => x.name).includes(tag)) { + // only add each tag once even if several custom modifier cards share the same tag + const imageModifierCard = modifierCard.cloneNode(true) + imageModifierCard.querySelector(".modifier-card-label p").innerText = tag.replace( + modifierName, + shortModifierName + ) + activeTags.push({ + name: tag, + element: imageModifierCard, + originElement: modifierCard + }) + } + modifierCard.classList.add(activeCardClass) + modifierCard.querySelector(".modifier-card-image-overlay").innerText = "-" + found = true } - modifierCard.classList.add(activeCardClass) - modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-' - found = true - } - }) - if (found == false) { // custom tag went missing, create one here + }) + if (found == false) { + // custom tag went missing, create one here let modifierCard = createModifierCard(tag, undefined, false) // create a modifier card for the missing tag, no image - - modifierCard.addEventListener('click', () => { - if (activeTags.map(x => x.name).includes(tag)) { + + modifierCard.addEventListener("click", () => { + if (activeTags.map((x) => x.name).includes(tag)) { // remove modifier from active array - activeTags = activeTags.filter(x => x.name != tag) + activeTags = activeTags.filter((x) => x.name != tag) modifierCard.classList.remove(activeCardClass) - modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+' + modifierCard.querySelector(".modifier-card-image-overlay").innerText = "+" } refreshTagsList() }) activeTags.push({ - 'name': tag, - 'element': modifierCard, - 'originElement': undefined // no origin element for missing tags + name: tag, + element: modifierCard, + originElement: undefined // no origin element for missing tags }) } }) @@ -220,41 +234,44 @@ function refreshModifiersState(newTags, inactiveTags) { function refreshInactiveTags(inactiveTags) { // update inactive tags if (inactiveTags !== undefined && inactiveTags.length > 0) { - activeTags.forEach (tag => { - if (inactiveTags.find(element => element === tag.name) !== undefined) { + activeTags.forEach((tag) => { + if (inactiveTags.find((element) => element === tag.name) !== undefined) { tag.inactive = true } }) } - + // update cards - let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay') - overlays.forEach (i => { - let modifierName = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].dataset.fullName - if (inactiveTags?.find(element => element === modifierName) !== undefined) { - i.parentElement.classList.add('modifier-toggle-inactive') + let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay") + overlays.forEach((i) => { + let modifierName = i.parentElement.getElementsByClassName("modifier-card-label")[0].getElementsByTagName("p")[0] + .dataset.fullName + if (inactiveTags?.find((element) => element === modifierName) !== undefined) { + i.parentElement.classList.add("modifier-toggle-inactive") } }) } function refreshTagsList(inactiveTags) { - editorModifierTagsList.innerHTML = '' + editorModifierTagsList.innerHTML = "" if (activeTags.length == 0) { - editorTagsContainer.style.display = 'none' + editorTagsContainer.style.display = "none" return } else { - editorTagsContainer.style.display = 'block' + editorTagsContainer.style.display = "block" } activeTags.forEach((tag, index) => { - tag.element.querySelector('.modifier-card-image-overlay').innerText = '-' - tag.element.classList.add('modifier-card-tiny') + tag.element.querySelector(".modifier-card-image-overlay").innerText = "-" + tag.element.classList.add("modifier-card-tiny") editorModifierTagsList.appendChild(tag.element) - tag.element.addEventListener('click', () => { - let idx = activeTags.findIndex(o => { return o.name === tag.name }) + tag.element.addEventListener("click", () => { + let idx = activeTags.findIndex((o) => { + return o.name === tag.name + }) if (idx !== -1) { toggleCardState(activeTags[idx].name, false) @@ -262,88 +279,91 @@ function refreshTagsList(inactiveTags) { activeTags.splice(idx, 1) refreshTagsList() } - document.dispatchEvent(new Event('refreshImageModifiers')) + document.dispatchEvent(new Event("refreshImageModifiers")) }) }) - let brk = document.createElement('br') - brk.style.clear = 'both' + let brk = document.createElement("br") + brk.style.clear = "both" editorModifierTagsList.appendChild(brk) refreshInactiveTags(inactiveTags) - document.dispatchEvent(new Event('refreshImageModifiers')) // notify plugins that the image tags have been refreshed + document.dispatchEvent(new Event("refreshImageModifiers")) // notify plugins that the image tags have been refreshed } function toggleCardState(modifierName, makeActive) { - document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(card => { - const name = card.querySelector('.modifier-card-label').innerText - if ( trimModifiers(modifierName) == trimModifiers(name) - || trimModifiers(modifierName) == 'by ' + trimModifiers(name)) { - if(makeActive) { - card.classList.add(activeCardClass) - card.querySelector('.modifier-card-image-overlay').innerText = '-' + document + .querySelector("#editor-modifiers") + .querySelectorAll(".modifier-card") + .forEach((card) => { + const name = card.querySelector(".modifier-card-label").innerText + if ( + trimModifiers(modifierName) == trimModifiers(name) || + trimModifiers(modifierName) == "by " + trimModifiers(name) + ) { + if (makeActive) { + card.classList.add(activeCardClass) + card.querySelector(".modifier-card-image-overlay").innerText = "-" + } else { + card.classList.remove(activeCardClass) + card.querySelector(".modifier-card-image-overlay").innerText = "+" + } } - else{ - card.classList.remove(activeCardClass) - card.querySelector('.modifier-card-image-overlay').innerText = '+' - } - } - }) + }) } function changePreviewImages(val) { - const previewImages = document.querySelectorAll('.modifier-card-image-container img') + const previewImages = document.querySelectorAll(".modifier-card-image-container img") let previewArr = [] - modifiers.map(x => x.modifiers).forEach(x => previewArr.push(...x.map(m => m.previews))) - - previewArr = previewArr.map(x => { + modifiers.map((x) => x.modifiers).forEach((x) => previewArr.push(...x.map((m) => m.previews))) + + previewArr = previewArr.map((x) => { let obj = {} - x.forEach(preview => { + x.forEach((preview) => { obj[preview.name] = preview.path }) - + return obj }) - previewImages.forEach(previewImage => { - const currentPreviewType = previewImage.getAttribute('preview-type') - const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + '/').pop() + previewImages.forEach((previewImage) => { + const currentPreviewType = previewImage.getAttribute("preview-type") + const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + "/").pop() - const previews = previewArr.find(preview => relativePreviewPath == preview[currentPreviewType]) + const previews = previewArr.find((preview) => relativePreviewPath == preview[currentPreviewType]) - if(typeof previews == 'object') { + if (typeof previews == "object") { let preview = null - if (val == 'portrait') { + if (val == "portrait") { preview = previews.portrait - } - else if (val == 'landscape') { + } else if (val == "landscape") { preview = previews.landscape } - if(preview != null) { + if (preview != null) { previewImage.src = `${modifierThumbnailPath}/${preview}` - previewImage.setAttribute('preview-type', val) + previewImage.setAttribute("preview-type", val) } } }) } function resizeModifierCards(val) { - const cardSizePrefix = 'modifier-card-size_' - const modifierCardClass = 'modifier-card' + const cardSizePrefix = "modifier-card-size_" + const modifierCardClass = "modifier-card" const modifierCards = document.querySelectorAll(`.${modifierCardClass}`) - const cardSize = n => `${cardSizePrefix}${n}` + const cardSize = (n) => `${cardSizePrefix}${n}` - modifierCards.forEach(card => { + modifierCards.forEach((card) => { // remove existing size classes - const classes = card.className.split(' ').filter(c => !c.startsWith(cardSizePrefix)) - card.className = classes.join(' ').trim() + const classes = card.className.split(" ").filter((c) => !c.startsWith(cardSizePrefix)) + card.className = classes.join(" ").trim() - if(val != 0) { + if (val != 0) { card.classList.add(cardSize(val)) } }) @@ -352,7 +372,7 @@ function resizeModifierCards(val) { modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value) previewImageField.onchange = () => changePreviewImages(previewImageField.value) -modifierSettingsBtn.addEventListener('click', function(e) { +modifierSettingsBtn.addEventListener("click", function(e) { modifierSettingsOverlay.classList.add("active") customModifiersTextBox.setSelectionRange(0, 0) customModifiersTextBox.focus() @@ -360,7 +380,7 @@ modifierSettingsBtn.addEventListener('click', function(e) { e.stopPropagation() }) -modifierSettingsOverlay.addEventListener('keydown', function(e) { +modifierSettingsOverlay.addEventListener("keydown", function(e) { switch (e.key) { case "Escape": // Escape to cancel customModifiersTextBox.value = customModifiersInitialContent // undo the changes @@ -368,7 +388,8 @@ modifierSettingsOverlay.addEventListener('keydown', function(e) { e.stopPropagation() break case "Enter": - if (e.ctrlKey) { // Ctrl+Enter to confirm + if (e.ctrlKey) { + // Ctrl+Enter to confirm modifierSettingsOverlay.classList.remove("active") e.stopPropagation() break @@ -383,7 +404,7 @@ function saveCustomModifiers() { } function loadCustomModifiers() { - PLUGINS['MODIFIERS_LOAD'].forEach(fn=>fn.loader.call()) + PLUGINS["MODIFIERS_LOAD"].forEach((fn) => fn.loader.call()) } -customModifiersTextBox.addEventListener('change', saveCustomModifiers) +customModifiersTextBox.addEventListener("change", saveCustomModifiers) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 0720b988..2f63650b 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -2,80 +2,97 @@ const MAX_INIT_IMAGE_DIMENSION = 768 const MIN_GPUS_TO_SHOW_SELECTION = 2 -const IMAGE_REGEX = new RegExp('data:image/[A-Za-z]+;base64') +const IMAGE_REGEX = new RegExp("data:image/[A-Za-z]+;base64") const htmlTaskMap = new WeakMap() const taskConfigSetup = { taskConfig: { - seed: { value: ({ seed }) => seed, label: 'Seed' }, - dimensions: { value: ({ reqBody }) => `${reqBody?.width}x${reqBody?.height}`, label: 'Dimensions' }, - sampler_name: 'Sampler', - num_inference_steps: 'Inference Steps', - guidance_scale: 'Guidance Scale', - use_stable_diffusion_model: 'Model', - use_vae_model: { label: 'VAE', visible: ({ reqBody }) => reqBody?.use_vae_model !== undefined && reqBody?.use_vae_model.trim() !== ''}, - negative_prompt: { label: 'Negative Prompt', visible: ({ reqBody }) => reqBody?.negative_prompt !== undefined && reqBody?.negative_prompt.trim() !== ''}, - prompt_strength: 'Prompt Strength', - use_face_correction: 'Fix Faces', - upscale: { value: ({ reqBody }) => `${reqBody?.use_upscale} (${reqBody?.upscale_amount || 4}x)`, label: 'Upscale', visible: ({ reqBody }) => !!reqBody?.use_upscale }, - use_hypernetwork_model: 'Hypernetwork', - hypernetwork_strength: { label: 'Hypernetwork Strength', visible: ({ reqBody }) => !!reqBody?.use_hypernetwork_model }, - use_lora_model: { label: 'Lora Model', visible: ({ reqBody }) => !!reqBody?.use_lora_model }, - lora_alpha: { label: 'Lora Strength', visible: ({ reqBody }) => !!reqBody?.use_lora_model }, - preserve_init_image_color_profile: 'Preserve Color Profile', + seed: { value: ({ seed }) => seed, label: "Seed" }, + dimensions: { value: ({ reqBody }) => `${reqBody?.width}x${reqBody?.height}`, label: "Dimensions" }, + sampler_name: "Sampler", + num_inference_steps: "Inference Steps", + guidance_scale: "Guidance Scale", + use_stable_diffusion_model: "Model", + use_vae_model: { + label: "VAE", + visible: ({ reqBody }) => reqBody?.use_vae_model !== undefined && reqBody?.use_vae_model.trim() !== "" + }, + negative_prompt: { + label: "Negative Prompt", + visible: ({ reqBody }) => reqBody?.negative_prompt !== undefined && reqBody?.negative_prompt.trim() !== "" + }, + prompt_strength: "Prompt Strength", + use_face_correction: "Fix Faces", + upscale: { + value: ({ reqBody }) => `${reqBody?.use_upscale} (${reqBody?.upscale_amount || 4}x)`, + label: "Upscale", + visible: ({ reqBody }) => !!reqBody?.use_upscale + }, + use_hypernetwork_model: "Hypernetwork", + hypernetwork_strength: { + label: "Hypernetwork Strength", + visible: ({ reqBody }) => !!reqBody?.use_hypernetwork_model + }, + use_lora_model: { label: "Lora Model", visible: ({ reqBody }) => !!reqBody?.use_lora_model }, + lora_alpha: { label: "Lora Strength", visible: ({ reqBody }) => !!reqBody?.use_lora_model }, + preserve_init_image_color_profile: "Preserve Color Profile" }, pluginTaskConfig: {}, - getCSSKey: (key) => key.split('_').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join('') + getCSSKey: (key) => + key + .split("_") + .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) + .join("") } let imageCounter = 0 let imageRequest = [] -let promptField = document.querySelector('#prompt') -let promptsFromFileSelector = document.querySelector('#prompt_from_file') -let promptsFromFileBtn = document.querySelector('#promptsFromFileBtn') -let negativePromptField = document.querySelector('#negative_prompt') -let numOutputsTotalField = document.querySelector('#num_outputs_total') -let numOutputsParallelField = document.querySelector('#num_outputs_parallel') -let numInferenceStepsField = document.querySelector('#num_inference_steps') -let guidanceScaleSlider = document.querySelector('#guidance_scale_slider') -let guidanceScaleField = document.querySelector('#guidance_scale') -let outputQualitySlider = document.querySelector('#output_quality_slider') -let outputQualityField = document.querySelector('#output_quality') -let outputQualityRow = document.querySelector('#output_quality_row') +let promptField = document.querySelector("#prompt") +let promptsFromFileSelector = document.querySelector("#prompt_from_file") +let promptsFromFileBtn = document.querySelector("#promptsFromFileBtn") +let negativePromptField = document.querySelector("#negative_prompt") +let numOutputsTotalField = document.querySelector("#num_outputs_total") +let numOutputsParallelField = document.querySelector("#num_outputs_parallel") +let numInferenceStepsField = document.querySelector("#num_inference_steps") +let guidanceScaleSlider = document.querySelector("#guidance_scale_slider") +let guidanceScaleField = document.querySelector("#guidance_scale") +let outputQualitySlider = document.querySelector("#output_quality_slider") +let outputQualityField = document.querySelector("#output_quality") +let outputQualityRow = document.querySelector("#output_quality_row") let randomSeedField = document.querySelector("#random_seed") -let seedField = document.querySelector('#seed') -let widthField = document.querySelector('#width') -let heightField = document.querySelector('#height') -let smallImageWarning = document.querySelector('#small_image_warning') +let seedField = document.querySelector("#seed") +let widthField = document.querySelector("#width") +let heightField = document.querySelector("#height") +let smallImageWarning = document.querySelector("#small_image_warning") let initImageSelector = document.querySelector("#init_image") let initImagePreview = document.querySelector("#init_image_preview") let initImageSizeBox = document.querySelector("#init_image_size_box") let maskImageSelector = document.querySelector("#mask") let maskImagePreview = document.querySelector("#mask_preview") -let applyColorCorrectionField = document.querySelector('#apply_color_correction') -let colorCorrectionSetting = document.querySelector('#apply_color_correction_setting') -let promptStrengthSlider = document.querySelector('#prompt_strength_slider') -let promptStrengthField = document.querySelector('#prompt_strength') -let samplerField = document.querySelector('#sampler_name') +let applyColorCorrectionField = document.querySelector("#apply_color_correction") +let colorCorrectionSetting = document.querySelector("#apply_color_correction_setting") +let promptStrengthSlider = document.querySelector("#prompt_strength_slider") +let promptStrengthField = document.querySelector("#prompt_strength") +let samplerField = document.querySelector("#sampler_name") let samplerSelectionContainer = document.querySelector("#samplerSelection") let useFaceCorrectionField = document.querySelector("#use_face_correction") -let gfpganModelField = new ModelDropdown(document.querySelector("#gfpgan_model"), 'gfpgan') +let gfpganModelField = new ModelDropdown(document.querySelector("#gfpgan_model"), "gfpgan") let useUpscalingField = document.querySelector("#use_upscale") let upscaleModelField = document.querySelector("#upscale_model") let upscaleAmountField = document.querySelector("#upscale_amount") -let stableDiffusionModelField = new ModelDropdown(document.querySelector('#stable_diffusion_model'), 'stable-diffusion') -let vaeModelField = new ModelDropdown(document.querySelector('#vae_model'), 'vae', 'None') -let hypernetworkModelField = new ModelDropdown(document.querySelector('#hypernetwork_model'), 'hypernetwork', 'None') -let hypernetworkStrengthSlider = document.querySelector('#hypernetwork_strength_slider') -let hypernetworkStrengthField = document.querySelector('#hypernetwork_strength') -let loraModelField = new ModelDropdown(document.querySelector('#lora_model'), 'lora', 'None') -let loraAlphaSlider = document.querySelector('#lora_alpha_slider') -let loraAlphaField = document.querySelector('#lora_alpha') -let outputFormatField = document.querySelector('#output_format') -let outputLosslessField = document.querySelector('#output_lossless') -let outputLosslessContainer = document.querySelector('#output_lossless_container') -let blockNSFWField = document.querySelector('#block_nsfw') +let stableDiffusionModelField = new ModelDropdown(document.querySelector("#stable_diffusion_model"), "stable-diffusion") +let vaeModelField = new ModelDropdown(document.querySelector("#vae_model"), "vae", "None") +let hypernetworkModelField = new ModelDropdown(document.querySelector("#hypernetwork_model"), "hypernetwork", "None") +let hypernetworkStrengthSlider = document.querySelector("#hypernetwork_strength_slider") +let hypernetworkStrengthField = document.querySelector("#hypernetwork_strength") +let loraModelField = new ModelDropdown(document.querySelector("#lora_model"), "lora", "None") +let loraAlphaSlider = document.querySelector("#lora_alpha_slider") +let loraAlphaField = document.querySelector("#lora_alpha") +let outputFormatField = document.querySelector("#output_format") +let outputLosslessField = document.querySelector("#output_lossless") +let outputLosslessContainer = document.querySelector("#output_lossless_container") +let blockNSFWField = document.querySelector("#block_nsfw") let showOnlyFilteredImageField = document.querySelector("#show_only_filtered_image") let updateBranchLabel = document.querySelector("#updateBranchLabel") let streamImageProgressField = document.querySelector("#stream_image_progress") @@ -83,16 +100,16 @@ let thumbnailSizeField = document.querySelector("#thumbnail_size-input") let autoscrollBtn = document.querySelector("#auto_scroll_btn") let autoScroll = document.querySelector("#auto_scroll") -let makeImageBtn = document.querySelector('#makeImage') -let stopImageBtn = document.querySelector('#stopImage') -let pauseBtn = document.querySelector('#pause') -let resumeBtn = document.querySelector('#resume') -let renderButtons = document.querySelector('#render-buttons') +let makeImageBtn = document.querySelector("#makeImage") +let stopImageBtn = document.querySelector("#stopImage") +let pauseBtn = document.querySelector("#pause") +let resumeBtn = document.querySelector("#resume") +let renderButtons = document.querySelector("#render-buttons") -let imagesContainer = document.querySelector('#current-images') -let initImagePreviewContainer = document.querySelector('#init_image_preview_container') -let initImageClearBtn = document.querySelector('.init_image_clear') -let promptStrengthContainer = document.querySelector('#prompt_strength_container') +let imagesContainer = document.querySelector("#current-images") +let initImagePreviewContainer = document.querySelector("#init_image_preview_container") +let initImageClearBtn = document.querySelector(".init_image_clear") +let promptStrengthContainer = document.querySelector("#prompt_strength_container") let initialText = document.querySelector("#initial-text") let previewTools = document.querySelector("#preview-tools") @@ -105,9 +122,9 @@ let saveAllTreeToggle = document.querySelector("#tree_toggle") let saveAllJSONToggle = document.querySelector("#json_toggle") let saveAllFoldersOption = document.querySelector("#download-add-folders") -let maskSetting = document.querySelector('#enable_mask') +let maskSetting = document.querySelector("#enable_mask") -const processOrder = document.querySelector('#process_order_toggle') +const processOrder = document.querySelector("#process_order_toggle") let imagePreview = document.querySelector("#preview") let imagePreviewContent = document.querySelector("#preview-content") @@ -116,8 +133,8 @@ let undoButton = document.querySelector("#undo") let undoBuffer = [] const UNDO_LIMIT = 20 -imagePreview.addEventListener('drop', function(ev) { - const data = ev.dataTransfer?.getData("text/plain"); +imagePreview.addEventListener("drop", function(ev) { + const data = ev.dataTransfer?.getData("text/plain") if (!data) { return } @@ -127,7 +144,7 @@ imagePreview.addEventListener('drop', function(ev) { } ev.preventDefault() let moveTarget = ev.target - while (moveTarget && typeof moveTarget === 'object' && moveTarget.parentNode !== imagePreviewContent) { + while (moveTarget && typeof moveTarget === "object" && moveTarget.parentNode !== imagePreviewContent) { moveTarget = moveTarget.parentNode } if (moveTarget === initialText || moveTarget === previewTools) { @@ -157,16 +174,14 @@ imagePreview.addEventListener('drop', function(ev) { } }) - - -let showConfigToggle = document.querySelector('#configToggleBtn') +let showConfigToggle = document.querySelector("#configToggleBtn") // let configBox = document.querySelector('#config') // let outputMsg = document.querySelector('#outputMsg') -let soundToggle = document.querySelector('#sound_toggle') +let soundToggle = document.querySelector("#sound_toggle") -let serverStatusColor = document.querySelector('#server-status-color') -let serverStatusMsg = document.querySelector('#server-status-msg') +let serverStatusColor = document.querySelector("#server-status-color") +let serverStatusMsg = document.querySelector("#server-status-msg") function getLocalStorageBoolItem(key, fallback) { let item = localStorage.getItem(key) @@ -174,7 +189,7 @@ function getLocalStorageBoolItem(key, fallback) { return fallback } - return (item === 'true' ? true : false) + return item === "true" ? true : false } function handleBoolSettingChange(key) { @@ -197,25 +212,24 @@ function getSavedDiskPath() { return getSetting("diskPath") } -function setStatus(statusType, msg, msgType) { -} +function setStatus(statusType, msg, msgType) {} function setServerStatus(event) { - switch(event.type) { - case 'online': - serverStatusColor.style.color = 'var(--status-green)' - serverStatusMsg.style.color = 'var(--status-green)' - serverStatusMsg.innerText = 'Stable Diffusion is ' + event.message + switch (event.type) { + case "online": + serverStatusColor.style.color = "var(--status-green)" + serverStatusMsg.style.color = "var(--status-green)" + serverStatusMsg.innerText = "Stable Diffusion is " + event.message break - case 'busy': - serverStatusColor.style.color = 'var(--status-orange)' - serverStatusMsg.style.color = 'var(--status-orange)' - serverStatusMsg.innerText = 'Stable Diffusion is ' + event.message + case "busy": + serverStatusColor.style.color = "var(--status-orange)" + serverStatusMsg.style.color = "var(--status-orange)" + serverStatusMsg.innerText = "Stable Diffusion is " + event.message break - case 'error': - serverStatusColor.style.color = 'var(--status-red)' - serverStatusMsg.style.color = 'var(--status-red)' - serverStatusMsg.innerText = 'Stable Diffusion has stopped' + case "error": + serverStatusColor.style.color = "var(--status-red)" + serverStatusMsg.style.color = "var(--status-red)" + serverStatusMsg.innerText = "Stable Diffusion has stopped" break } if (SD.serverState.devices) { @@ -229,37 +243,40 @@ function setServerStatus(event) { // fn : function to be called if the user confirms the dialog or has the shift key pressed // // If the user had the shift key pressed while clicking, the function fn will be executed. -// If the setting "confirm_dangerous_actions" in the system settings is disabled, the function +// If the setting "confirm_dangerous_actions" in the system settings is disabled, the function // fn will be executed. // Otherwise, a confirmation dialog is shown. If the user confirms, the function fn will also // be executed. function shiftOrConfirm(e, prompt, fn) { e.stopPropagation() if (e.shiftKey || !confirmDangerousActionsField.checked) { - fn(e) + fn(e) } else { $.confirm({ - theme: 'modern', + theme: "modern", title: prompt, useBootstrap: false, animateFromElement: false, - content: 'Tip: To skip this dialog, use shift-click or disable the "Confirm dangerous actions" setting in the Settings tab.', + content: + 'Tip: To skip this dialog, use shift-click or disable the "Confirm dangerous actions" setting in the Settings tab.', buttons: { - yes: () => { fn(e) }, + yes: () => { + fn(e) + }, cancel: () => {} } - }); + }) } } function logMsg(msg, level, outputMsg) { if (outputMsg.hasChildNodes()) { - outputMsg.appendChild(document.createElement('br')) + outputMsg.appendChild(document.createElement("br")) } - if (level === 'error') { - outputMsg.innerHTML += 'Error: ' + msg + '' - } else if (level === 'warn') { - outputMsg.innerHTML += 'Warning: ' + msg + '' + if (level === "error") { + outputMsg.innerHTML += 'Error: ' + msg + "" + } else if (level === "warn") { + outputMsg.innerHTML += 'Warning: ' + msg + "" } else { outputMsg.innerText += msg } @@ -267,35 +284,45 @@ function logMsg(msg, level, outputMsg) { } function logError(msg, res, outputMsg) { - logMsg(msg, 'error', outputMsg) + logMsg(msg, "error", outputMsg) - console.log('request error', res) - setStatus('request', 'error', 'error') + console.log("request error", res) + setStatus("request", "error", "error") } function playSound() { - const audio = new Audio('/media/ding.mp3') + const audio = new Audio("/media/ding.mp3") audio.volume = 0.2 var promise = audio.play() if (promise !== undefined) { - promise.then(_ => {}).catch(error => { - console.warn("browser blocked autoplay") - }) + promise + .then((_) => {}) + .catch((error) => { + console.warn("browser blocked autoplay") + }) } } -function undoableRemove(element, doubleUndo=false) { - let data = { 'element': element, 'parent': element.parentNode, 'prev': element.previousSibling, 'next': element.nextSibling, 'doubleUndo': doubleUndo } +function undoableRemove(element, doubleUndo = false) { + let data = { + element: element, + parent: element.parentNode, + prev: element.previousSibling, + next: element.nextSibling, + doubleUndo: doubleUndo + } undoBuffer.push(data) if (undoBuffer.length > UNDO_LIMIT) { // Remove item from memory and also remove it from the data structures let item = undoBuffer.shift() htmlTaskMap.delete(item.element) - item.element.querySelectorAll('[data-imagecounter]').forEach( (img) => { delete imageRequest[img.dataset['imagecounter']] }) + item.element.querySelectorAll("[data-imagecounter]").forEach((img) => { + delete imageRequest[img.dataset["imagecounter"]] + }) } element.remove() if (undoBuffer.length != 0) { - undoButton.classList.remove('displayNone') + undoButton.classList.remove("displayNone") } } @@ -313,42 +340,44 @@ function undoRemove() { undoRemove() } if (undoBuffer.length == 0) { - undoButton.classList.add('displayNone') + undoButton.classList.add("displayNone") } updateInitialText() } -undoButton.addEventListener('click', () => { undoRemove() }) +undoButton.addEventListener("click", () => { + undoRemove() +}) -document.addEventListener('keydown', function(e) { - if ((e.ctrlKey || e.metaKey) && e.key === 'z' && e.target == document.body) { +document.addEventListener("keydown", function(e) { + if ((e.ctrlKey || e.metaKey) && e.key === "z" && e.target == document.body) { undoRemove() } }) function showImages(reqBody, res, outputContainer, livePreview) { - let imageItemElements = outputContainer.querySelectorAll('.imgItem') - if(typeof res != 'object') return + let imageItemElements = outputContainer.querySelectorAll(".imgItem") + if (typeof res != "object") return res.output.reverse() res.output.forEach((result, index) => { - const imageData = result?.data || result?.path + '?t=' + Date.now(), + const imageData = result?.data || result?.path + "?t=" + Date.now(), imageSeed = result?.seed, imagePrompt = reqBody.prompt, imageInferenceSteps = reqBody.num_inference_steps, imageGuidanceScale = reqBody.guidance_scale, imageWidth = reqBody.width, - imageHeight = reqBody.height; + imageHeight = reqBody.height - if (!imageData.includes('/')) { + if (!imageData.includes("/")) { // res contained no data for the image, stop execution - setStatus('request', 'invalid image', 'error') + setStatus("request", "invalid image", "error") return } - let imageItemElem = (index < imageItemElements.length ? imageItemElements[index] : null) - if(!imageItemElem) { - imageItemElem = document.createElement('div') - imageItemElem.className = 'imgItem' + let imageItemElem = index < imageItemElements.length ? imageItemElements[index] : null + if (!imageItemElem) { + imageItemElem = document.createElement("div") + imageItemElem.className = "imgItem" imageItemElem.innerHTML = `
@@ -362,50 +391,52 @@ function showImages(reqBody, res, outputContainer, livePreview) {
` outputContainer.appendChild(imageItemElem) - const imageRemoveBtn = imageItemElem.querySelector('.imgPreviewItemClearBtn') - let parentTaskContainer = imageRemoveBtn.closest('.imageTaskContainer') - imageRemoveBtn.addEventListener('click', (e) => { + const imageRemoveBtn = imageItemElem.querySelector(".imgPreviewItemClearBtn") + let parentTaskContainer = imageRemoveBtn.closest(".imageTaskContainer") + imageRemoveBtn.addEventListener("click", (e) => { undoableRemove(imageItemElem) - let allHidden = true; - let children = parentTaskContainer.querySelectorAll('.imgItem'); - for(let x = 0; x < children.length; x++) { - let child = children[x]; - if(child.style.display != "none") { - allHidden = false; + let allHidden = true + let children = parentTaskContainer.querySelectorAll(".imgItem") + for (let x = 0; x < children.length; x++) { + let child = children[x] + if (child.style.display != "none") { + allHidden = false } } - if(allHidden === true) { + if (allHidden === true) { const req = htmlTaskMap.get(parentTaskContainer) - if(!req.isProcessing || req.batchesDone == req.batchCount) { undoableRemove(parentTaskContainer, true) } + if (!req.isProcessing || req.batchesDone == req.batchCount) { + undoableRemove(parentTaskContainer, true) + } } }) } - const imageElem = imageItemElem.querySelector('img') + const imageElem = imageItemElem.querySelector("img") imageElem.src = imageData imageElem.width = parseInt(imageWidth) imageElem.height = parseInt(imageHeight) - imageElem.setAttribute('data-prompt', imagePrompt) - imageElem.setAttribute('data-steps', imageInferenceSteps) - imageElem.setAttribute('data-guidance', imageGuidanceScale) + imageElem.setAttribute("data-prompt", imagePrompt) + imageElem.setAttribute("data-steps", imageInferenceSteps) + imageElem.setAttribute("data-guidance", imageGuidanceScale) - imageElem.addEventListener('load', function() { - imageItemElem.querySelector('.img_bottom_label').innerText = `${this.naturalWidth} x ${this.naturalHeight}` + imageElem.addEventListener("load", function() { + imageItemElem.querySelector(".img_bottom_label").innerText = `${this.naturalWidth} x ${this.naturalHeight}` }) - const imageInfo = imageItemElem.querySelector('.imgItemInfo') - imageInfo.style.visibility = (livePreview ? 'hidden' : 'visible') + const imageInfo = imageItemElem.querySelector(".imgItemInfo") + imageInfo.style.visibility = livePreview ? "hidden" : "visible" - if ('seed' in result && !imageElem.hasAttribute('data-seed')) { - const imageExpandBtn = imageItemElem.querySelector('.imgExpandBtn') - imageExpandBtn.addEventListener('click', function() { + if ("seed" in result && !imageElem.hasAttribute("data-seed")) { + const imageExpandBtn = imageItemElem.querySelector(".imgExpandBtn") + imageExpandBtn.addEventListener("click", function() { function previousImage(img) { - const allImages = Array.from(outputContainer.parentNode.querySelectorAll('.imgItem img')) + const allImages = Array.from(outputContainer.parentNode.querySelectorAll(".imgItem img")) const index = allImages.indexOf(img) return allImages.slice(0, index).reverse()[0] } function nextImage(img) { - const allImages = Array.from(outputContainer.parentNode.querySelectorAll('.imgItem img')) + const allImages = Array.from(outputContainer.parentNode.querySelectorAll(".imgItem img")) const index = allImages.indexOf(img) return allImages.slice(index + 1)[0] } @@ -417,7 +448,7 @@ function showImages(reqBody, res, outputContainer, livePreview) { return { src: img.src, previous: previousImg ? () => imageModalParameter(previousImg) : undefined, - next: nextImg ? () => imageModalParameter(nextImg) : undefined, + next: nextImg ? () => imageModalParameter(nextImg) : undefined } } @@ -427,62 +458,68 @@ function showImages(reqBody, res, outputContainer, livePreview) { const req = Object.assign({}, reqBody, { seed: result?.seed || reqBody.seed }) - imageElem.setAttribute('data-seed', req.seed) - imageElem.setAttribute('data-imagecounter', ++imageCounter) + imageElem.setAttribute("data-seed", req.seed) + imageElem.setAttribute("data-imagecounter", ++imageCounter) imageRequest[imageCounter] = req - const imageSeedLabel = imageItemElem.querySelector('.imgSeedLabel') - imageSeedLabel.innerText = 'Seed: ' + req.seed + const imageSeedLabel = imageItemElem.querySelector(".imgSeedLabel") + imageSeedLabel.innerText = "Seed: " + req.seed let buttons = [ - { text: 'Use as Input', on_click: onUseAsInputClick }, + { text: "Use as Input", on_click: onUseAsInputClick }, [ - { html: ' Download Image', on_click: onDownloadImageClick, class: "download-img" }, - { html: ' JSON', on_click: onDownloadJSONClick, class: "download-json" } + { + html: ' Download Image', + on_click: onDownloadImageClick, + class: "download-img" + }, + { + html: ' JSON', + on_click: onDownloadJSONClick, + class: "download-json" + } ], - { text: 'Make Similar Images', on_click: onMakeSimilarClick }, - { text: 'Draw another 25 steps', on_click: onContinueDrawingClick }, + { text: "Make Similar Images", on_click: onMakeSimilarClick }, + { text: "Draw another 25 steps", on_click: onContinueDrawingClick }, [ - { text: 'Upscale', on_click: onUpscaleClick, filter: (req, img) => !req.use_upscale }, - { text: 'Fix Faces', on_click: onFixFacesClick, filter: (req, img) => !req.use_face_correction } + { text: "Upscale", on_click: onUpscaleClick, filter: (req, img) => !req.use_upscale }, + { text: "Fix Faces", on_click: onFixFacesClick, filter: (req, img) => !req.use_face_correction } ] ] // include the plugins - buttons = buttons.concat(PLUGINS['IMAGE_INFO_BUTTONS']) + buttons = buttons.concat(PLUGINS["IMAGE_INFO_BUTTONS"]) - const imgItemInfo = imageItemElem.querySelector('.imgItemInfo') - const img = imageItemElem.querySelector('img') + const imgItemInfo = imageItemElem.querySelector(".imgItemInfo") + const img = imageItemElem.querySelector("img") const createButton = function(btnInfo) { if (Array.isArray(btnInfo)) { - const wrapper = document.createElement('div'); - btnInfo - .map(createButton) - .forEach(buttonElement => wrapper.appendChild(buttonElement)) + const wrapper = document.createElement("div") + btnInfo.map(createButton).forEach((buttonElement) => wrapper.appendChild(buttonElement)) return wrapper } - const isLabel = btnInfo.type === 'label' + const isLabel = btnInfo.type === "label" - const newButton = document.createElement(isLabel ? 'span' : 'button') - newButton.classList.add('tasksBtns') + const newButton = document.createElement(isLabel ? "span" : "button") + newButton.classList.add("tasksBtns") if (btnInfo.html) { - const html = typeof btnInfo.html === 'function' ? btnInfo.html() : btnInfo.html + const html = typeof btnInfo.html === "function" ? btnInfo.html() : btnInfo.html if (html instanceof HTMLElement) { newButton.appendChild(html) } else { newButton.innerHTML = html } } else { - newButton.innerText = typeof btnInfo.text === 'function' ? btnInfo.text() : btnInfo.text + newButton.innerText = typeof btnInfo.text === "function" ? btnInfo.text() : btnInfo.text } if (btnInfo.on_click || !isLabel) { - newButton.addEventListener('click', function(event) { + newButton.addEventListener("click", function(event) { btnInfo.on_click(req, img, event) }) } - + if (btnInfo.class !== undefined) { if (Array.isArray(btnInfo.class)) { newButton.classList.add(...btnInfo.class) @@ -492,9 +529,9 @@ function showImages(reqBody, res, outputContainer, livePreview) { } return newButton } - buttons.forEach(btn => { + buttons.forEach((btn) => { if (Array.isArray(btn)) { - btn = btn.filter(btnInfo => !btnInfo.filter || btnInfo.filter(req, img) === true) + btn = btn.filter((btnInfo) => !btnInfo.filter || btnInfo.filter(req, img) === true) if (btn.length === 0) { return } @@ -505,7 +542,7 @@ function showImages(reqBody, res, outputContainer, livePreview) { try { imgItemInfo.appendChild(createButton(btn)) } catch (err) { - console.error('Error creating image info button from plugin: ', btn, err) + console.error("Error creating image info button from plugin: ", btn, err) } }) } @@ -522,22 +559,22 @@ function onUseAsInputClick(req, img) { } function getDownloadFilename(img, suffix) { - const imageSeed = img.getAttribute('data-seed') - const imagePrompt = img.getAttribute('data-prompt') - const imageInferenceSteps = img.getAttribute('data-steps') - const imageGuidanceScale = img.getAttribute('data-guidance') - + const imageSeed = img.getAttribute("data-seed") + const imagePrompt = img.getAttribute("data-prompt") + const imageInferenceSteps = img.getAttribute("data-steps") + const imageGuidanceScale = img.getAttribute("data-guidance") + return createFileName(imagePrompt, imageSeed, imageInferenceSteps, imageGuidanceScale, suffix) } function onDownloadJSONClick(req, img) { - const name = getDownloadFilename(img, 'json') - const blob = new Blob([JSON.stringify(req, null, 2)], { type: 'text/plain' }) + const name = getDownloadFilename(img, "json") + const blob = new Blob([JSON.stringify(req, null, 2)], { type: "text/plain" }) saveAs(blob, name) } function onDownloadImageClick(req, img) { - const name = getDownloadFilename(img, req['output_format']) + const name = getDownloadFilename(img, req["output_format"]) const blob = dataURItoBlob(img.src) saveAs(blob, name) } @@ -572,7 +609,7 @@ function onMakeSimilarClick(req, img) { } function enqueueImageVariationTask(req, img, reqDiff) { - const imageSeed = img.getAttribute('data-seed') + const imageSeed = img.getAttribute("data-seed") const newRequestBody = { num_outputs: 1, // this can be user-configurable in the future @@ -581,10 +618,10 @@ function enqueueImageVariationTask(req, img, reqDiff) { // If the user is editing pictures, stop modifyCurrentRequest from importing // new values by setting the missing properties to undefined - if (!('init_image' in req) && !('init_image' in reqDiff)) { + if (!("init_image" in req) && !("init_image" in reqDiff)) { newRequestBody.init_image = undefined newRequestBody.mask = undefined - } else if (!('mask' in req) && !('mask' in reqDiff)) { + } else if (!("mask" in req) && !("mask" in reqDiff)) { newRequestBody.mask = undefined } @@ -614,12 +651,11 @@ function onContinueDrawingClick(req, img) { } function getUncompletedTaskEntries() { - const taskEntries = Array.from( - document.querySelectorAll('#preview .imageTaskContainer .taskStatusLabel') - ).filter((taskLabel) => taskLabel.style.display !== 'none' - ).map(function(taskLabel) { + const taskEntries = Array.from(document.querySelectorAll("#preview .imageTaskContainer .taskStatusLabel")) + .filter((taskLabel) => taskLabel.style.display !== "none") + .map(function(taskLabel) { let imageTaskContainer = taskLabel.parentNode - while(!imageTaskContainer.classList.contains('imageTaskContainer') && imageTaskContainer.parentNode) { + while (!imageTaskContainer.classList.contains("imageTaskContainer") && imageTaskContainer.parentNode) { imageTaskContainer = imageTaskContainer.parentNode } return imageTaskContainer @@ -632,34 +668,36 @@ function getUncompletedTaskEntries() { function makeImage() { if (typeof performance == "object" && performance.mark) { - performance.mark('click-makeImage') + performance.mark("click-makeImage") } if (!SD.isServerAvailable()) { - alert('The server is not available.') + alert("The server is not available.") return } - if (!randomSeedField.checked && seedField.value == '') { + if (!randomSeedField.checked && seedField.value == "") { alert('The "Seed" field must not be empty.') return } - if (numInferenceStepsField.value == '') { + if (numInferenceStepsField.value == "") { alert('The "Inference Steps" field must not be empty.') return } - if (numOutputsTotalField.value == '' || numOutputsTotalField.value == 0) { + if (numOutputsTotalField.value == "" || numOutputsTotalField.value == 0) { numOutputsTotalField.value = 1 } - if (numOutputsParallelField.value == '' || numOutputsParallelField.value == 0) { + if (numOutputsParallelField.value == "" || numOutputsParallelField.value == 0) { numOutputsParallelField.value = 1 } - if (guidanceScaleField.value == '') { + if (guidanceScaleField.value == "") { guidanceScaleField.value = guidanceScaleSlider.value / 10 } const taskTemplate = getCurrentUserRequest() - const newTaskRequests = getPrompts().map((prompt) => Object.assign({}, taskTemplate, { - reqBody: Object.assign({ prompt: prompt }, taskTemplate.reqBody) - })) + const newTaskRequests = getPrompts().map((prompt) => + Object.assign({}, taskTemplate, { + reqBody: Object.assign({ prompt: prompt }, taskTemplate.reqBody) + }) + ) newTaskRequests.forEach(createTask) updateInitialText() @@ -667,7 +705,7 @@ function makeImage() { async function onIdle() { const serverCapacity = SD.serverCapacity - if (pauseClient===true) { + if (pauseClient === true) { await resumeClient() } @@ -677,8 +715,8 @@ async function onIdle() { } const task = htmlTaskMap.get(taskEntry) if (!task) { - const taskStatusLabel = taskEntry.querySelector('.taskStatusLabel') - taskStatusLabel.style.display = 'none' + const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel") + taskStatusLabel.style.display = "none" continue } await onTaskStart(task) @@ -686,8 +724,8 @@ async function onIdle() { } function getTaskUpdater(task, reqBody, outputContainer) { - const outputMsg = task['outputMsg'] - const progressBar = task['progressBar'] + const outputMsg = task["outputMsg"] + const progressBar = task["progressBar"] const progressBarInner = progressBar.querySelector("div") const batchCount = task.batchCount @@ -695,60 +733,73 @@ function getTaskUpdater(task, reqBody, outputContainer) { return async function(event) { if (this.status !== lastStatus) { lastStatus = this.status - switch(this.status) { + switch (this.status) { case SD.TaskStatus.pending: - task['taskStatusLabel'].innerText = "Pending" - task['taskStatusLabel'].classList.add('waitingTaskLabel') + task["taskStatusLabel"].innerText = "Pending" + task["taskStatusLabel"].classList.add("waitingTaskLabel") break case SD.TaskStatus.waiting: - task['taskStatusLabel'].innerText = "Waiting" - task['taskStatusLabel'].classList.add('waitingTaskLabel') - task['taskStatusLabel'].classList.remove('activeTaskLabel') + task["taskStatusLabel"].innerText = "Waiting" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + task["taskStatusLabel"].classList.remove("activeTaskLabel") break case SD.TaskStatus.processing: case SD.TaskStatus.completed: - task['taskStatusLabel'].innerText = "Processing" - task['taskStatusLabel'].classList.add('activeTaskLabel') - task['taskStatusLabel'].classList.remove('waitingTaskLabel') + task["taskStatusLabel"].innerText = "Processing" + task["taskStatusLabel"].classList.add("activeTaskLabel") + task["taskStatusLabel"].classList.remove("waitingTaskLabel") break case SD.TaskStatus.stopped: break case SD.TaskStatus.failed: if (!SD.isServerAvailable()) { - logError("Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed. Please check the error message in the command-line window.", event, outputMsg) - } else if (typeof event?.response === 'object') { - let msg = 'Stable Diffusion had an error reading the response:
'
+                        logError(
+                            "Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed. Please check the error message in the command-line window.",
+                            event,
+                            outputMsg
+                        )
+                    } else if (typeof event?.response === "object") {
+                        let msg = "Stable Diffusion had an error reading the response:
"
                         if (this.exception) {
                             msg += `Error: ${this.exception.message}
` } - try { // 'Response': body stream already read - msg += 'Read: ' + await event.response.text() - } catch(e) { - msg += 'Unexpected end of stream. ' + try { + // 'Response': body stream already read + msg += "Read: " + (await event.response.text()) + } catch (e) { + msg += "Unexpected end of stream. " } const bufferString = event.reader.bufferedString if (bufferString) { - msg += 'Buffered data: ' + bufferString + msg += "Buffered data: " + bufferString } - msg += '
' + msg += "
" logError(msg, event, outputMsg) } else { - let msg = `Unexpected Read Error:
Error:${this.exception}
EventInfo: ${JSON.stringify(event, undefined, 4)}
` + let msg = `Unexpected Read Error:
Error:${
+                            this.exception
+                        }
EventInfo: ${JSON.stringify(event, undefined, 4)}
` logError(msg, event, outputMsg) } break } } - if ('update' in event) { + if ("update" in event) { const stepUpdate = event.update - if (!('step' in stepUpdate)) { + if (!("step" in stepUpdate)) { return } // task.instances can be a mix of different tasks with uneven number of steps (Render Vs Filter Tasks) - const overallStepCount = task.instances.reduce( - (sum, instance) => sum + (instance.isPending ? Math.max(0, instance.step || stepUpdate.step) / (instance.total_steps || stepUpdate.total_steps) : 1), - 0 // Initial value - ) * stepUpdate.total_steps // Scale to current number of steps. + const overallStepCount = + task.instances.reduce( + (sum, instance) => + sum + + (instance.isPending + ? Math.max(0, instance.step || stepUpdate.step) / + (instance.total_steps || stepUpdate.total_steps) + : 1), + 0 // Initial value + ) * stepUpdate.total_steps // Scale to current number of steps. const totalSteps = task.instances.reduce( (sum, instance) => sum + (instance.total_steps || stepUpdate.total_steps), stepUpdate.total_steps * (batchCount - task.batchesDone) // Initial value at (unstarted task count * Nbr of steps) @@ -757,9 +808,9 @@ function getTaskUpdater(task, reqBody, outputContainer) { const timeTaken = stepUpdate.step_time // sec const stepsRemaining = Math.max(0, totalSteps - overallStepCount) - const timeRemaining = (timeTaken < 0 ? '' : millisecondsToStr(stepsRemaining * timeTaken * 1000)) + const timeRemaining = timeTaken < 0 ? "" : millisecondsToStr(stepsRemaining * timeTaken * 1000) outputMsg.innerHTML = `Batch ${task.batchesDone} of ${batchCount}. Generating image(s): ${percent}%. Time remaining (approx): ${timeRemaining}` - outputMsg.style.display = 'block' + outputMsg.style.display = "block" progressBarInner.style.width = `${percent}%` if (stepUpdate.output) { @@ -775,8 +826,8 @@ function abortTask(task) { } task.isProcessing = false task.progressBar.classList.remove("active") - task['taskStatusLabel'].style.display = 'none' - task['stopTask'].innerHTML = ' Remove' + task["taskStatusLabel"].style.display = "none" + task["stopTask"].innerHTML = ' Remove' if (!task.instances?.some((r) => r.isPending)) { return } @@ -793,24 +844,32 @@ function onTaskErrorHandler(task, reqBody, instance, reason) { if (!task.isProcessing) { return } - console.log('Render request %o, Instance: %o, Error: %s', reqBody, instance, reason) + console.log("Render request %o, Instance: %o, Error: %s", reqBody, instance, reason) abortTask(task) - const outputMsg = task['outputMsg'] - logError('Stable Diffusion had an error. Please check the logs in the command-line window.

' + reason + '
' + reason.stack + '
', task, outputMsg) - setStatus('request', 'error', 'error') + const outputMsg = task["outputMsg"] + logError( + "Stable Diffusion had an error. Please check the logs in the command-line window.

" + + reason + + "
" +
+            reason.stack +
+            "
", + task, + outputMsg + ) + setStatus("request", "error", "error") } function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { - if (typeof stepUpdate === 'object') { - if (stepUpdate.status === 'succeeded') { + if (typeof stepUpdate === "object") { + if (stepUpdate.status === "succeeded") { showImages(reqBody, stepUpdate, outputContainer, false) } else { task.isProcessing = false - const outputMsg = task['outputMsg'] - let msg = '' - if ('detail' in stepUpdate && typeof stepUpdate.detail === 'string' && stepUpdate.detail.length > 0) { + const outputMsg = task["outputMsg"] + let msg = "" + if ("detail" in stepUpdate && typeof stepUpdate.detail === "string" && stepUpdate.detail.length > 0) { msg = stepUpdate.detail - if (msg.toLowerCase().includes('out of memory')) { + if (msg.toLowerCase().includes("out of memory")) { msg += `

Suggestions:
@@ -825,29 +884,29 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { } } if (task.isProcessing && task.batchesDone < task.batchCount) { - task['taskStatusLabel'].innerText = "Pending" - task['taskStatusLabel'].classList.add('waitingTaskLabel') - task['taskStatusLabel'].classList.remove('activeTaskLabel') + task["taskStatusLabel"].innerText = "Pending" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + task["taskStatusLabel"].classList.remove("activeTaskLabel") return } - if ('instances' in task && task.instances.some((ins) => ins != instance && ins.isPending)) { + if ("instances" in task && task.instances.some((ins) => ins != instance && ins.isPending)) { return } task.isProcessing = false - task['stopTask'].innerHTML = ' Remove' - task['taskStatusLabel'].style.display = 'none' + task["stopTask"].innerHTML = ' Remove' + task["taskStatusLabel"].style.display = "none" - let time = millisecondsToStr( Date.now() - task.startTime ) + let time = millisecondsToStr(Date.now() - task.startTime) if (task.batchesDone == task.batchCount) { - if (!task.outputMsg.innerText.toLowerCase().includes('error')) { + if (!task.outputMsg.innerText.toLowerCase().includes("error")) { task.outputMsg.innerText = `Processed ${task.numOutputsTotal} images in ${time}` } task.progressBar.style.height = "0px" task.progressBar.style.border = "0px solid var(--background-color3)" task.progressBar.classList.remove("active") - setStatus('request', 'done', 'success') + setStatus("request", "done", "success") } else { task.outputMsg.innerText += `. Task ended after ${time}` } @@ -864,10 +923,10 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { return } - if (pauseClient) { - resumeBtn.click() + if (pauseClient) { + resumeBtn.click() } - renderButtons.style.display = 'none' + renderButtons.style.display = "none" renameMakeImageButton() if (isSoundEnabled()) { @@ -875,39 +934,40 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { } } - async function onTaskStart(task) { if (!task.isProcessing || task.batchesDone >= task.batchCount) { return } - if (typeof task.startTime !== 'number') { + if (typeof task.startTime !== "number") { task.startTime = Date.now() } - if (!('instances' in task)) { - task['instances'] = [] + if (!("instances" in task)) { + task["instances"] = [] } - task['stopTask'].innerHTML = ' Stop' - task['taskStatusLabel'].innerText = "Starting" - task['taskStatusLabel'].classList.add('waitingTaskLabel') + task["stopTask"].innerHTML = ' Stop' + task["taskStatusLabel"].innerText = "Starting" + task["taskStatusLabel"].classList.add("waitingTaskLabel") let newTaskReqBody = task.reqBody if (task.batchCount > 1) { // Each output render batch needs it's own task reqBody instance to avoid altering the other runs after they are completed. newTaskReqBody = Object.assign({}, task.reqBody) - if (task.batchesDone == task.batchCount-1) { + if (task.batchesDone == task.batchCount - 1) { // Last batch of the task // If the number of parallel jobs is no factor of the total number of images, the last batch must create less than "parallel jobs count" images // E.g. with numOutputsTotal = 6 and num_outputs = 5, the last batch shall only generate 1 image. - newTaskReqBody.num_outputs = task.numOutputsTotal - task.reqBody.num_outputs * (task.batchCount-1) + newTaskReqBody.num_outputs = task.numOutputsTotal - task.reqBody.num_outputs * (task.batchCount - 1) } } const startSeed = task.seed || newTaskReqBody.seed - const genSeeds = Boolean(typeof newTaskReqBody.seed !== 'number' || (newTaskReqBody.seed === task.seed && task.numOutputsTotal > 1)) + const genSeeds = Boolean( + typeof newTaskReqBody.seed !== "number" || (newTaskReqBody.seed === task.seed && task.numOutputsTotal > 1) + ) if (genSeeds) { - newTaskReqBody.seed = parseInt(startSeed) + (task.batchesDone * task.reqBody.num_outputs) + newTaskReqBody.seed = parseInt(startSeed) + task.batchesDone * task.reqBody.num_outputs } // Update the seed *before* starting the processing so it's retained if user stops the task @@ -915,15 +975,15 @@ async function onTaskStart(task) { seedField.value = task.seed } - const outputContainer = document.createElement('div') - outputContainer.className = 'img-batch' + const outputContainer = document.createElement("div") + outputContainer.className = "img-batch" task.outputContainer.insertBefore(outputContainer, task.outputContainer.firstChild) - const eventInfo = {reqBody:newTaskReqBody} - const callbacksPromises = PLUGINS['TASK_CREATE'].map((hook) => { - if (typeof hook !== 'function') { - console.error('The provided TASK_CREATE hook is not a function. Hook: %o', hook) - return Promise.reject(new Error('hook is not a function.')) + const eventInfo = { reqBody: newTaskReqBody } + const callbacksPromises = PLUGINS["TASK_CREATE"].map((hook) => { + if (typeof hook !== "function") { + console.error("The provided TASK_CREATE hook is not a function. Hook: %o", hook) + return Promise.reject(new Error("hook is not a function.")) } try { return Promise.resolve(hook.call(task, eventInfo)) @@ -940,12 +1000,16 @@ async function onTaskStart(task) { instance = await Promise.resolve(factory(eventInfo.reqBody || newTaskReqBody)) } 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.` + ) instance = new SD.RenderTask(eventInfo.reqBody || newTaskReqBody) } } - task['instances'].push(instance) + task["instances"].push(instance) task.batchesDone++ instance.enqueue(getTaskUpdater(task, newTaskReqBody, outputContainer)).then( @@ -957,49 +1021,49 @@ async function onTaskStart(task) { } ) - setStatus('request', 'fetching..') - renderButtons.style.display = 'flex' + setStatus("request", "fetching..") + renderButtons.style.display = "flex" renameMakeImageButton() updateInitialText() } /* Hover effect for the init image in the task list */ function createInitImageHover(taskEntry) { - var $tooltip = $( taskEntry.querySelector('.task-fs-initimage') ) - var img = document.createElement('img') - img.src = taskEntry.querySelector('div.task-initimg > img').src + var $tooltip = $(taskEntry.querySelector(".task-fs-initimage")) + var img = document.createElement("img") + img.src = taskEntry.querySelector("div.task-initimg > img").src $tooltip.append(img) $tooltip.append(`
`) - $tooltip.find('button').on('click', (e) => { + $tooltip.find("button").on("click", (e) => { e.stopPropagation() - onUseAsInputClick(null,img) + onUseAsInputClick(null, img) }) } -let startX, startY; +let startX, startY function onTaskEntryDragOver(event) { - imagePreview.querySelectorAll(".imageTaskContainer").forEach(itc => { - if(itc != event.target.closest(".imageTaskContainer")){ - itc.classList.remove('dropTargetBefore','dropTargetAfter'); + imagePreview.querySelectorAll(".imageTaskContainer").forEach((itc) => { + if (itc != event.target.closest(".imageTaskContainer")) { + itc.classList.remove("dropTargetBefore", "dropTargetAfter") } - }); - if(event.target.closest(".imageTaskContainer")){ - if(startX && startY){ - if(event.target.closest(".imageTaskContainer").offsetTop > startY){ - event.target.closest(".imageTaskContainer").classList.add('dropTargetAfter'); - }else if(event.target.closest(".imageTaskContainer").offsetTop < startY){ - event.target.closest(".imageTaskContainer").classList.add('dropTargetBefore'); - }else if (event.target.closest(".imageTaskContainer").offsetLeft > startX){ - event.target.closest(".imageTaskContainer").classList.add('dropTargetAfter'); - }else if (event.target.closest(".imageTaskContainer").offsetLeft < startX){ - event.target.closest(".imageTaskContainer").classList.add('dropTargetBefore'); + }) + if (event.target.closest(".imageTaskContainer")) { + if (startX && startY) { + if (event.target.closest(".imageTaskContainer").offsetTop > startY) { + event.target.closest(".imageTaskContainer").classList.add("dropTargetAfter") + } else if (event.target.closest(".imageTaskContainer").offsetTop < startY) { + event.target.closest(".imageTaskContainer").classList.add("dropTargetBefore") + } else if (event.target.closest(".imageTaskContainer").offsetLeft > startX) { + event.target.closest(".imageTaskContainer").classList.add("dropTargetAfter") + } else if (event.target.closest(".imageTaskContainer").offsetLeft < startX) { + event.target.closest(".imageTaskContainer").classList.add("dropTargetBefore") } } } } function generateConfig({ label, value, visible, cssKey }) { - if (!visible) return null; + if (!visible) return null return `
${label}: ${value}` } @@ -1014,27 +1078,27 @@ function getVisibleConfig(config, task) { return { label, visible, value, cssKey } }) .map((obj) => generateConfig(obj)) - .filter(obj => obj) + .filter((obj) => obj) } function createTaskConfig(task) { - return getVisibleConfig(taskConfigSetup, task).join('
') + return getVisibleConfig(taskConfigSetup, task).join(", ") } function createTask(task) { - let taskConfig = '' + let taskConfig = "" if (task.reqBody.init_image !== undefined) { let h = 80 - let w = task.reqBody.width * h / task.reqBody.height >>0 + let w = ((task.reqBody.width * h) / task.reqBody.height) >> 0 taskConfig += `
` } - taskConfig += `
${createTaskConfig(task)}
`; + taskConfig += `
${createTaskConfig(task)}
` - let taskEntry = document.createElement('div') + let taskEntry = document.createElement("div") taskEntry.id = `imageTaskContainer-${Date.now()}` - taskEntry.className = 'imageTaskContainer' + taskEntry.className = "imageTaskContainer" taskEntry.innerHTML = `
Enqueued
@@ -1051,46 +1115,49 @@ function createTask(task) { createCollapsibles(taskEntry) - let draghandle = taskEntry.querySelector('.drag-handle') - draghandle.addEventListener('mousedown', (e) => { - taskEntry.setAttribute('draggable', true) + let draghandle = taskEntry.querySelector(".drag-handle") + draghandle.addEventListener("mousedown", (e) => { + taskEntry.setAttribute("draggable", true) }) // Add a debounce delay to allow mobile to bouble tap. - draghandle.addEventListener('mouseup', debounce((e) => { - taskEntry.setAttribute('draggable', false) - }, 2000)) - draghandle.addEventListener('click', (e) => { + draghandle.addEventListener( + "mouseup", + debounce((e) => { + taskEntry.setAttribute("draggable", false) + }, 2000) + ) + draghandle.addEventListener("click", (e) => { e.preventDefault() // Don't allow the results to be collapsed... }) - taskEntry.addEventListener('dragend', (e) => { - taskEntry.setAttribute('draggable', false); - imagePreview.querySelectorAll(".imageTaskContainer").forEach(itc => { - itc.classList.remove('dropTargetBefore','dropTargetAfter'); - }); - imagePreview.removeEventListener("dragover", onTaskEntryDragOver ); + taskEntry.addEventListener("dragend", (e) => { + taskEntry.setAttribute("draggable", false) + imagePreview.querySelectorAll(".imageTaskContainer").forEach((itc) => { + itc.classList.remove("dropTargetBefore", "dropTargetAfter") + }) + imagePreview.removeEventListener("dragover", onTaskEntryDragOver) }) - taskEntry.addEventListener('dragstart', function(e) { - imagePreview.addEventListener("dragover", onTaskEntryDragOver ); - e.dataTransfer.setData("text/plain", taskEntry.id); - startX = e.target.closest(".imageTaskContainer").offsetLeft; - startY = e.target.closest(".imageTaskContainer").offsetTop; + taskEntry.addEventListener("dragstart", function(e) { + imagePreview.addEventListener("dragover", onTaskEntryDragOver) + e.dataTransfer.setData("text/plain", taskEntry.id) + startX = e.target.closest(".imageTaskContainer").offsetLeft + startY = e.target.closest(".imageTaskContainer").offsetTop }) if (task.reqBody.init_image !== undefined) { createInitImageHover(taskEntry) } - task['taskStatusLabel'] = taskEntry.querySelector('.taskStatusLabel') - task['outputContainer'] = taskEntry.querySelector('.img-preview') - task['outputMsg'] = taskEntry.querySelector('.outputMsg') - task['previewPrompt'] = taskEntry.querySelector('.preview-prompt') - task['progressBar'] = taskEntry.querySelector('.progress-bar') - task['stopTask'] = taskEntry.querySelector('.stopTask') + task["taskStatusLabel"] = taskEntry.querySelector(".taskStatusLabel") + task["outputContainer"] = taskEntry.querySelector(".img-preview") + task["outputMsg"] = taskEntry.querySelector(".outputMsg") + task["previewPrompt"] = taskEntry.querySelector(".preview-prompt") + task["progressBar"] = taskEntry.querySelector(".progress-bar") + task["stopTask"] = taskEntry.querySelector(".stopTask") - task['stopTask'].addEventListener('click', (e) => { + task["stopTask"].addEventListener("click", (e) => { e.stopPropagation() - if (task['isProcessing']) { + if (task["isProcessing"]) { shiftOrConfirm(e, "Stop this task?", async function(e) { if (task.batchesDone <= 0 || !task.isProcessing) { removeTask(taskEntry) @@ -1102,8 +1169,8 @@ function createTask(task) { } }) - task['useSettings'] = taskEntry.querySelector('.useSettings') - task['useSettings'].addEventListener('click', function(e) { + task["useSettings"] = taskEntry.querySelector(".useSettings") + task["useSettings"].addEventListener("click", function(e) { e.stopPropagation() restoreTaskToUI(task, TASK_REQ_NO_EXPORT) }) @@ -1113,8 +1180,8 @@ function createTask(task) { htmlTaskMap.set(taskEntry, task) task.previewPrompt.innerText = task.reqBody.prompt - if (task.previewPrompt.innerText.trim() === '') { - task.previewPrompt.innerHTML = ' ' // allows the results to be collapsed + if (task.previewPrompt.innerText.trim() === "") { + task.previewPrompt.innerHTML = " " // allows the results to be collapsed } return taskEntry.id } @@ -1122,7 +1189,7 @@ function createTask(task) { function getCurrentUserRequest() { const numOutputsTotal = parseInt(numOutputsTotalField.value) const numOutputsParallel = parseInt(numOutputsParallelField.value) - const seed = (randomSeedField.checked ? Math.floor(Math.random() * (2**32 - 1)) : parseInt(seedField.value)) + const seed = randomSeedField.checked ? Math.floor(Math.random() * (2 ** 32 - 1)) : parseInt(seedField.value) const newTask = { batchesDone: 0, @@ -1145,7 +1212,7 @@ function getCurrentUserRequest() { use_stable_diffusion_model: stableDiffusionModelField.value, use_vae_model: vaeModelField.value, stream_progress_updates: true, - stream_image_progress: (numOutputsTotal > 50 ? false : streamImageProgressField.checked), + stream_image_progress: numOutputsTotal > 50 ? false : streamImageProgressField.checked, show_only_filtered_image: showOnlyFilteredImageField.checked, block_nsfw: blockNSFWField.checked, output_format: outputFormatField.value, @@ -1153,8 +1220,8 @@ function getCurrentUserRequest() { output_lossless: outputLosslessField.checked, metadata_output_format: metadataOutputFormatField.value, original_prompt: promptField.value, - active_tags: (activeTags.map(x => x.name)), - inactive_tags: (activeTags.filter(tag => tag.inactive === true).map(x => x.name)) + active_tags: activeTags.map((x) => x.name), + inactive_tags: activeTags.filter((tag) => tag.inactive === true).map((x) => x.name) } } if (IMAGE_REGEX.test(initImagePreview.src)) { @@ -1168,10 +1235,10 @@ function getCurrentUserRequest() { } newTask.reqBody.preserve_init_image_color_profile = applyColorCorrectionField.checked if (!testDiffusers.checked) { - newTask.reqBody.sampler_name = 'ddim' + newTask.reqBody.sampler_name = "ddim" } } - if (saveToDiskField.checked && diskPathField.value.trim() !== '') { + if (saveToDiskField.checked && diskPathField.value.trim() !== "") { newTask.reqBody.save_to_disk_path = diskPathField.value.trim() } if (useFaceCorrectionField.checked) { @@ -1193,30 +1260,28 @@ function getCurrentUserRequest() { } function getPrompts(prompts) { - if (typeof prompts === 'undefined') { + if (typeof prompts === "undefined") { prompts = promptField.value } - if (prompts.trim() === '' && activeTags.length === 0) { - return [''] + if (prompts.trim() === "" && activeTags.length === 0) { + return [""] } let promptsToMake = [] - if (prompts.trim() !== '') { - prompts = prompts.split('\n') - prompts = prompts.map(prompt => prompt.trim()) - prompts = prompts.filter(prompt => prompt !== '') - + if (prompts.trim() !== "") { + prompts = prompts.split("\n") + prompts = prompts.map((prompt) => prompt.trim()) + prompts = prompts.filter((prompt) => prompt !== "") + promptsToMake = applyPermuteOperator(prompts) promptsToMake = applySetOperator(promptsToMake) } - const newTags = activeTags.filter(tag => tag.inactive === undefined || tag.inactive === false) + const newTags = activeTags.filter((tag) => tag.inactive === undefined || tag.inactive === false) if (newTags.length > 0) { - const promptTags = newTags.map(x => x.name).join(", ") + const promptTags = newTags.map((x) => x.name).join(", ") if (promptsToMake.length > 0) { promptsToMake = promptsToMake.map((prompt) => `${prompt}, ${promptTags}`) - } - else - { + } else { promptsToMake.push(promptTags) } } @@ -1224,7 +1289,9 @@ function getPrompts(prompts) { promptsToMake = applyPermuteOperator(promptsToMake) promptsToMake = applySetOperator(promptsToMake) - PLUGINS['GET_PROMPTS_HOOK'].forEach(fn => { promptsToMake = fn(promptsToMake) }) + PLUGINS["GET_PROMPTS_HOOK"].forEach((fn) => { + promptsToMake = fn(promptsToMake) + }) return promptsToMake } @@ -1232,7 +1299,7 @@ function getPrompts(prompts) { function applySetOperator(prompts) { let promptsToMake = [] let braceExpander = new BraceExpander() - prompts.forEach(prompt => { + prompts.forEach((prompt) => { let expandedPrompts = braceExpander.expand(prompt) promptsToMake = promptsToMake.concat(expandedPrompts) }) @@ -1242,13 +1309,13 @@ function applySetOperator(prompts) { function applyPermuteOperator(prompts) { let promptsToMake = [] - prompts.forEach(prompt => { - let promptMatrix = prompt.split('|') + prompts.forEach((prompt) => { + let promptMatrix = prompt.split("|") prompt = promptMatrix.shift().trim() promptsToMake.push(prompt) - promptMatrix = promptMatrix.map(p => p.trim()) - promptMatrix = promptMatrix.filter(p => p !== '') + promptMatrix = promptMatrix.map((p) => p.trim()) + promptMatrix = promptMatrix.filter((p) => p !== "") if (promptMatrix.length > 0) { let promptPermutations = permutePrompts(prompt, promptMatrix) @@ -1262,16 +1329,16 @@ function applyPermuteOperator(prompts) { function permutePrompts(promptBase, promptMatrix) { let prompts = [] let permutations = permute(promptMatrix) - permutations.forEach(perm => { + permutations.forEach((perm) => { let prompt = promptBase if (perm.length > 0) { - let promptAddition = perm.join(', ') - if (promptAddition.trim() === '') { + let promptAddition = perm.join(", ") + if (promptAddition.trim() === "") { return } - prompt += ', ' + promptAddition + prompt += ", " + promptAddition } prompts.push(prompt) @@ -1283,9 +1350,8 @@ function permutePrompts(promptBase, promptMatrix) { // create a file name with embedded prompt and metadata // for easier cateloging and comparison function createFileName(prompt, seed, steps, guidance, outputFormat) { - // Most important information is the prompt - let underscoreName = prompt.replace(/[^a-zA-Z0-9]/g, '_') + let underscoreName = prompt.replace(/[^a-zA-Z0-9]/g, "_") underscoreName = underscoreName.substring(0, 70) // name and the top level metadata @@ -1296,9 +1362,9 @@ function createFileName(prompt, seed, steps, guidance, outputFormat) { async function stopAllTasks() { getUncompletedTaskEntries().forEach((taskEntry) => { - const taskStatusLabel = taskEntry.querySelector('.taskStatusLabel') + const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel") if (taskStatusLabel) { - taskStatusLabel.style.display = 'none' + taskStatusLabel.style.display = "none" } const task = htmlTaskMap.get(taskEntry) if (!task) { @@ -1309,16 +1375,16 @@ async function stopAllTasks() { } function updateInitialText() { - if (document.querySelector('.imageTaskContainer') === null) { + if (document.querySelector(".imageTaskContainer") === null) { if (undoBuffer.length > 0) { initialText.prepend(undoButton) } - previewTools.classList.add('displayNone') - initialText.classList.remove('displayNone') + previewTools.classList.add("displayNone") + initialText.classList.remove("displayNone") } else { - initialText.classList.add('displayNone') - previewTools.classList.remove('displayNone') - document.querySelector('div.display-settings').prepend(undoButton) + initialText.classList.add("displayNone") + previewTools.classList.remove("displayNone") + document.querySelector("div.display-settings").prepend(undoButton) } } @@ -1327,30 +1393,37 @@ function removeTask(taskToRemove) { updateInitialText() } -clearAllPreviewsBtn.addEventListener('click', (e) => { shiftOrConfirm(e, "Clear all the results and tasks in this window?", async function() { - await stopAllTasks() +clearAllPreviewsBtn.addEventListener("click", (e) => { + shiftOrConfirm(e, "Clear all the results and tasks in this window?", async function() { + await stopAllTasks() - let taskEntries = document.querySelectorAll('.imageTaskContainer') - taskEntries.forEach(removeTask) -})}) + let taskEntries = document.querySelectorAll(".imageTaskContainer") + taskEntries.forEach(removeTask) + }) +}) /* Download images popup */ -showDownloadPopupBtn.addEventListener("click", (e) => { saveAllImagesPopup.classList.add("active") }) +showDownloadPopupBtn.addEventListener("click", (e) => { + saveAllImagesPopup.classList.add("active") +}) -saveAllZipToggle.addEventListener('change', (e) => { +saveAllZipToggle.addEventListener("change", (e) => { if (saveAllZipToggle.checked) { - saveAllFoldersOption.classList.remove('displayNone') + saveAllFoldersOption.classList.remove("displayNone") } else { - saveAllFoldersOption.classList.add('displayNone') + saveAllFoldersOption.classList.add("displayNone") } }) // convert base64 to raw binary data held in a string function dataURItoBlob(dataURI) { - var byteString = atob(dataURI.split(',')[1]) + var byteString = atob(dataURI.split(",")[1]) // separate out the mime component - var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] + var mimeString = dataURI + .split(",")[0] + .split(":")[1] + .split(";")[0] // write the bytes of the string to an ArrayBuffer var ab = new ArrayBuffer(byteString.length) @@ -1364,97 +1437,112 @@ function dataURItoBlob(dataURI) { } // write the ArrayBuffer to a blob, and you're done - return new Blob([ab], {type: mimeString}) + return new Blob([ab], { type: mimeString }) } function downloadAllImages() { let i = 0 - let optZIP = saveAllZipToggle.checked + let optZIP = saveAllZipToggle.checked let optTree = optZIP && saveAllTreeToggle.checked let optJSON = saveAllJSONToggle.checked - + let zip = new JSZip() let folder = zip - document.querySelectorAll(".imageTaskContainer").forEach(container => { + document.querySelectorAll(".imageTaskContainer").forEach((container) => { if (optTree) { - let name = ++i + '-' + container.querySelector('.preview-prompt').textContent.replace(/[^a-zA-Z0-9]/g, '_').substring(0,25) + let name = + ++i + + "-" + + container + .querySelector(".preview-prompt") + .textContent.replace(/[^a-zA-Z0-9]/g, "_") + .substring(0, 25) folder = zip.folder(name) } - container.querySelectorAll(".imgContainer img").forEach(img => { - let imgItem = img.closest('.imgItem') + container.querySelectorAll(".imgContainer img").forEach((img) => { + let imgItem = img.closest(".imgItem") - if (imgItem.style.display === 'none') { + if (imgItem.style.display === "none") { return } - let req = imageRequest[img.dataset['imagecounter']] + let req = imageRequest[img.dataset["imagecounter"]] if (optZIP) { - let suffix = img.dataset['imagecounter'] + '.' + req['output_format'] + let suffix = img.dataset["imagecounter"] + "." + req["output_format"] folder.file(getDownloadFilename(img, suffix), dataURItoBlob(img.src)) if (optJSON) { - suffix = img.dataset['imagecounter'] + '.json' + suffix = img.dataset["imagecounter"] + ".json" folder.file(getDownloadFilename(img, suffix), JSON.stringify(req, null, 2)) } } else { - setTimeout(() => {imgItem.querySelector('.download-img').click()}, i*200) - i = i+1 + setTimeout(() => { + imgItem.querySelector(".download-img").click() + }, i * 200) + i = i + 1 if (optJSON) { - setTimeout(() => {imgItem.querySelector('.download-json').click()}, i*200) - i = i+1 + setTimeout(() => { + imgItem.querySelector(".download-json").click() + }, i * 200) + i = i + 1 } } }) }) if (optZIP) { - let now = Date.now().toString(36).toUpperCase() - zip.generateAsync({type:"blob"}).then(function (blob) { - saveAs(blob, `EasyDiffusion-Images-${now}.zip`); + let now = Date.now() + .toString(36) + .toUpperCase() + zip.generateAsync({ type: "blob" }).then(function(blob) { + saveAs(blob, `EasyDiffusion-Images-${now}.zip`) }) } +} -} +saveAllImagesBtn.addEventListener("click", (e) => { + downloadAllImages() +}) -saveAllImagesBtn.addEventListener('click', (e) => { downloadAllImages() }) +stopImageBtn.addEventListener("click", (e) => { + shiftOrConfirm(e, "Stop all the tasks?", async function(e) { + await stopAllTasks() + }) +}) -stopImageBtn.addEventListener('click', (e) => { shiftOrConfirm(e, "Stop all the tasks?", async function(e) { - await stopAllTasks() -})}) - -widthField.addEventListener('change', onDimensionChange) -heightField.addEventListener('change', onDimensionChange) +widthField.addEventListener("change", onDimensionChange) +heightField.addEventListener("change", onDimensionChange) function renameMakeImageButton() { - let totalImages = Math.max(parseInt(numOutputsTotalField.value), parseInt(numOutputsParallelField.value)) * getPrompts().length - let imageLabel = 'Image' + let totalImages = + Math.max(parseInt(numOutputsTotalField.value), parseInt(numOutputsParallelField.value)) * getPrompts().length + let imageLabel = "Image" if (totalImages > 1) { - imageLabel = totalImages + ' Images' + imageLabel = totalImages + " Images" } if (SD.activeTasks.size == 0) { - makeImageBtn.innerText = 'Make ' + imageLabel + makeImageBtn.innerText = "Make " + imageLabel } else { - makeImageBtn.innerText = 'Enqueue Next ' + imageLabel + makeImageBtn.innerText = "Enqueue Next " + imageLabel } } -numOutputsTotalField.addEventListener('change', renameMakeImageButton) -numOutputsTotalField.addEventListener('keyup', debounce(renameMakeImageButton, 300)) -numOutputsParallelField.addEventListener('change', renameMakeImageButton) -numOutputsParallelField.addEventListener('keyup', debounce(renameMakeImageButton, 300)) +numOutputsTotalField.addEventListener("change", renameMakeImageButton) +numOutputsTotalField.addEventListener("keyup", debounce(renameMakeImageButton, 300)) +numOutputsParallelField.addEventListener("change", renameMakeImageButton) +numOutputsParallelField.addEventListener("keyup", debounce(renameMakeImageButton, 300)) function onDimensionChange() { let widthValue = parseInt(widthField.value) let heightValue = parseInt(heightField.value) if (!initImagePreviewContainer.classList.contains("has-image")) { imageEditor.setImage(null, widthValue, heightValue) - } - else { + } else { imageInpainter.setImage(initImagePreview.src, widthValue, heightValue) } - if ( widthValue < 512 && heightValue < 512 ) { - smallImageWarning.classList.remove('displayNone') + if (widthValue < 512 && heightValue < 512) { + smallImageWarning.classList.remove("displayNone") } else { - smallImageWarning.classList.add('displayNone') + smallImageWarning.classList.add("displayNone") } } @@ -1462,21 +1550,21 @@ diskPathField.disabled = !saveToDiskField.checked metadataOutputFormatField.disabled = !saveToDiskField.checked gfpganModelField.disabled = !useFaceCorrectionField.checked -useFaceCorrectionField.addEventListener('change', function(e) { +useFaceCorrectionField.addEventListener("change", function(e) { gfpganModelField.disabled = !this.checked }) upscaleModelField.disabled = !useUpscalingField.checked upscaleAmountField.disabled = !useUpscalingField.checked -useUpscalingField.addEventListener('change', function(e) { +useUpscalingField.addEventListener("change", function(e) { upscaleModelField.disabled = !this.checked upscaleAmountField.disabled = !this.checked }) -makeImageBtn.addEventListener('click', makeImage) +makeImageBtn.addEventListener("click", makeImage) document.onkeydown = function(e) { - if (e.ctrlKey && e.code === 'Enter') { + if (e.ctrlKey && e.code === "Enter") { makeImage() e.preventDefault() } @@ -1499,8 +1587,8 @@ function updateGuidanceScaleSlider() { guidanceScaleSlider.dispatchEvent(new Event("change")) } -guidanceScaleSlider.addEventListener('input', updateGuidanceScale) -guidanceScaleField.addEventListener('input', updateGuidanceScaleSlider) +guidanceScaleSlider.addEventListener("input", updateGuidanceScale) +guidanceScaleField.addEventListener("input", updateGuidanceScaleSlider) updateGuidanceScale() /********************* Prompt Strength *******************/ @@ -1520,8 +1608,8 @@ function updatePromptStrengthSlider() { promptStrengthSlider.dispatchEvent(new Event("change")) } -promptStrengthSlider.addEventListener('input', updatePromptStrength) -promptStrengthField.addEventListener('input', updatePromptStrengthSlider) +promptStrengthSlider.addEventListener("input", updatePromptStrength) +promptStrengthField.addEventListener("input", updatePromptStrengthSlider) updatePromptStrength() /********************* Hypernetwork Strength **********************/ @@ -1541,14 +1629,15 @@ function updateHypernetworkStrengthSlider() { hypernetworkStrengthSlider.dispatchEvent(new Event("change")) } -hypernetworkStrengthSlider.addEventListener('input', updateHypernetworkStrength) -hypernetworkStrengthField.addEventListener('input', updateHypernetworkStrengthSlider) +hypernetworkStrengthSlider.addEventListener("input", updateHypernetworkStrength) +hypernetworkStrengthField.addEventListener("input", updateHypernetworkStrengthSlider) updateHypernetworkStrength() function updateHypernetworkStrengthContainer() { - document.querySelector("#hypernetwork_strength_container").style.display = (hypernetworkModelField.value === "" ? 'none' : '') + document.querySelector("#hypernetwork_strength_container").style.display = + hypernetworkModelField.value === "" ? "none" : "" } -hypernetworkModelField.addEventListener('change', updateHypernetworkStrengthContainer) +hypernetworkModelField.addEventListener("change", updateHypernetworkStrengthContainer) updateHypernetworkStrengthContainer() /********************* LoRA alpha **********************/ @@ -1568,19 +1657,19 @@ function updateLoraAlphaSlider() { loraAlphaSlider.dispatchEvent(new Event("change")) } -loraAlphaSlider.addEventListener('input', updateLoraAlpha) -loraAlphaField.addEventListener('input', updateLoraAlphaSlider) +loraAlphaSlider.addEventListener("input", updateLoraAlpha) +loraAlphaField.addEventListener("input", updateLoraAlphaSlider) updateLoraAlpha() function updateLoraAlphaContainer() { - document.querySelector("#lora_alpha_container").style.display = (loraModelField.value === "" ? 'none' : '') + document.querySelector("#lora_alpha_container").style.display = loraModelField.value === "" ? "none" : "" } -loraModelField.addEventListener('change', updateLoraAlphaContainer) +loraModelField.addEventListener("change", updateLoraAlphaContainer) updateLoraAlphaContainer() /********************* JPEG/WEBP Quality **********************/ function updateOutputQuality() { - outputQualityField.value = 0 | outputQualitySlider.value + outputQualityField.value = 0 | outputQualitySlider.value outputQualityField.dispatchEvent(new Event("change")) } @@ -1591,45 +1680,44 @@ function updateOutputQualitySlider() { outputQualityField.value = 95 } - outputQualitySlider.value = 0 | outputQualityField.value + outputQualitySlider.value = 0 | outputQualityField.value outputQualitySlider.dispatchEvent(new Event("change")) } -outputQualitySlider.addEventListener('input', updateOutputQuality) -outputQualityField.addEventListener('input', debounce(updateOutputQualitySlider, 1500)) +outputQualitySlider.addEventListener("input", updateOutputQuality) +outputQualityField.addEventListener("input", debounce(updateOutputQualitySlider, 1500)) updateOutputQuality() function updateOutputQualityVisibility() { - if (outputFormatField.value === 'webp') { - outputLosslessContainer.classList.remove('displayNone') + if (outputFormatField.value === "webp") { + outputLosslessContainer.classList.remove("displayNone") if (outputLosslessField.checked) { - outputQualityRow.classList.add('displayNone') + outputQualityRow.classList.add("displayNone") } else { - outputQualityRow.classList.remove('displayNone') + outputQualityRow.classList.remove("displayNone") } - } - else if (outputFormatField.value === 'png') { - outputQualityRow.classList.add('displayNone') - outputLosslessContainer.classList.add('displayNone') + } else if (outputFormatField.value === "png") { + outputQualityRow.classList.add("displayNone") + outputLosslessContainer.classList.add("displayNone") } else { - outputQualityRow.classList.remove('displayNone') - outputLosslessContainer.classList.add('displayNone') + outputQualityRow.classList.remove("displayNone") + outputLosslessContainer.classList.add("displayNone") } } -outputFormatField.addEventListener('change', updateOutputQualityVisibility) -outputLosslessField.addEventListener('change', updateOutputQualityVisibility) +outputFormatField.addEventListener("change", updateOutputQualityVisibility) +outputLosslessField.addEventListener("change", updateOutputQualityVisibility) /********************* Zoom Slider **********************/ -thumbnailSizeField.addEventListener('change', () => { - (function (s) { - for (var j =0; j < document.styleSheets.length; j++) { +thumbnailSizeField.addEventListener("change", () => { + ;(function(s) { + for (var j = 0; j < document.styleSheets.length; j++) { let cssSheet = document.styleSheets[j] for (var i = 0; i < cssSheet.cssRules.length; i++) { - var rule = cssSheet.cssRules[i]; + var rule = cssSheet.cssRules[i] if (rule.selectorText == "div.img-preview img") { - rule.style['max-height'] = s+'vh'; - rule.style['max-width'] = s+'vw'; - return; + rule.style["max-height"] = s + "vh" + rule.style["max-width"] = s + "vw" + return } } } @@ -1638,18 +1726,18 @@ thumbnailSizeField.addEventListener('change', () => { function onAutoScrollUpdate() { if (autoScroll.checked) { - autoscrollBtn.classList.add('pressed') + autoscrollBtn.classList.add("pressed") } else { - autoscrollBtn.classList.remove('pressed') + autoscrollBtn.classList.remove("pressed") } - autoscrollBtn.querySelector(".state").innerHTML = (autoScroll.checked ? "ON" : "OFF") + autoscrollBtn.querySelector(".state").innerHTML = autoScroll.checked ? "ON" : "OFF" } -autoscrollBtn.addEventListener('click', function() { +autoscrollBtn.addEventListener("click", function() { autoScroll.checked = !autoScroll.checked autoScroll.dispatchEvent(new Event("change")) onAutoScrollUpdate() }) -autoScroll.addEventListener('change', onAutoScrollUpdate) +autoScroll.addEventListener("change", onAutoScrollUpdate) function checkRandomSeed() { if (randomSeedField.checked) { @@ -1659,7 +1747,7 @@ function checkRandomSeed() { seedField.disabled = false } } -randomSeedField.addEventListener('input', checkRandomSeed) +randomSeedField.addEventListener("input", checkRandomSeed) checkRandomSeed() function loadImg2ImgFromFile() { @@ -1670,7 +1758,7 @@ function loadImg2ImgFromFile() { let reader = new FileReader() let file = initImageSelector.files[0] - reader.addEventListener('load', function(event) { + reader.addEventListener("load", function(event) { initImagePreview.src = reader.result }) @@ -1678,16 +1766,16 @@ function loadImg2ImgFromFile() { reader.readAsDataURL(file) } } -initImageSelector.addEventListener('change', loadImg2ImgFromFile) +initImageSelector.addEventListener("change", loadImg2ImgFromFile) loadImg2ImgFromFile() function img2imgLoad() { - promptStrengthContainer.style.display = 'table-row' + promptStrengthContainer.style.display = "table-row" if (!testDiffusers.checked) { samplerSelectionContainer.style.display = "none" } initImagePreviewContainer.classList.add("has-image") - colorCorrectionSetting.style.display = '' + colorCorrectionSetting.style.display = "" initImageSizeBox.textContent = initImagePreview.naturalWidth + " x " + initImagePreview.naturalHeight imageEditor.setImage(this.src, initImagePreview.naturalWidth, initImagePreview.naturalHeight) @@ -1696,7 +1784,7 @@ function img2imgLoad() { function img2imgUnload() { initImageSelector.value = null - initImagePreview.src = '' + initImagePreview.src = "" maskSetting.checked = false promptStrengthContainer.style.display = "none" @@ -1704,22 +1792,21 @@ function img2imgUnload() { samplerSelectionContainer.style.display = "" } initImagePreviewContainer.classList.remove("has-image") - colorCorrectionSetting.style.display = 'none' + colorCorrectionSetting.style.display = "none" imageEditor.setImage(null, parseInt(widthField.value), parseInt(heightField.value)) - } -initImagePreview.addEventListener('load', img2imgLoad) -initImageClearBtn.addEventListener('click', img2imgUnload) +initImagePreview.addEventListener("load", img2imgLoad) +initImageClearBtn.addEventListener("click", img2imgUnload) -maskSetting.addEventListener('click', function() { +maskSetting.addEventListener("click", function() { onDimensionChange() }) -promptsFromFileBtn.addEventListener('click', function() { +promptsFromFileBtn.addEventListener("click", function() { promptsFromFileSelector.click() }) -promptsFromFileSelector.addEventListener('change', async function() { +promptsFromFileSelector.addEventListener("change", async function() { if (promptsFromFileSelector.files.length === 0) { return } @@ -1727,7 +1814,7 @@ promptsFromFileSelector.addEventListener('change', async function() { let reader = new FileReader() let file = promptsFromFileSelector.files[0] - reader.addEventListener('load', async function() { + reader.addEventListener("load", async function() { await parseContent(reader.result) }) @@ -1737,15 +1824,15 @@ promptsFromFileSelector.addEventListener('change', async function() { }) /* setup popup handlers */ -document.querySelectorAll('.popup').forEach(popup => { - popup.addEventListener('click', event => { +document.querySelectorAll(".popup").forEach((popup) => { + popup.addEventListener("click", (event) => { if (event.target == popup) { popup.classList.remove("active") } }) var closeButton = popup.querySelector(".close-button") if (closeButton) { - closeButton.addEventListener('click', () => { + closeButton.addEventListener("click", () => { popup.classList.remove("active") }) } @@ -1753,9 +1840,9 @@ document.querySelectorAll('.popup').forEach(popup => { var tabElements = [] function selectTab(tab_id) { - let tabInfo = tabElements.find(t => t.tab.id == tab_id) + let tabInfo = tabElements.find((t) => t.tab.id == tab_id) if (!tabInfo.tab.classList.contains("active")) { - tabElements.forEach(info => { + tabElements.forEach((info) => { if (info.tab.classList.contains("active") && info.tab.parentNode === tabInfo.tab.parentNode) { info.tab.classList.toggle("active") info.content.classList.toggle("active") @@ -1764,7 +1851,7 @@ function selectTab(tab_id) { tabInfo.tab.classList.toggle("active") tabInfo.content.classList.toggle("active") } - document.dispatchEvent(new CustomEvent('tabClick', { detail: tabInfo })) + document.dispatchEvent(new CustomEvent("tabClick", { detail: tabInfo })) } function linkTabContents(tab) { var name = tab.id.replace("tab-", "") @@ -1775,7 +1862,7 @@ function linkTabContents(tab) { content: content }) - tab.addEventListener("click", event => selectTab(tab.id)) + tab.addEventListener("click", (event) => selectTab(tab.id)) } function isTabActive(tab) { return tab.classList.contains("active") @@ -1785,54 +1872,53 @@ let pauseClient = false function resumeClient() { if (pauseClient) { - document.body.classList.remove('wait-pause') - document.body.classList.add('pause') + document.body.classList.remove("wait-pause") + document.body.classList.add("pause") } - return new Promise(resolve => { - let playbuttonclick = function () { - resumeBtn.removeEventListener("click", playbuttonclick); - resolve("resolved"); + return new Promise((resolve) => { + let playbuttonclick = function() { + resumeBtn.removeEventListener("click", playbuttonclick) + resolve("resolved") } resumeBtn.addEventListener("click", playbuttonclick) }) } -promptField.addEventListener("input", debounce( renameMakeImageButton, 1000) ) +promptField.addEventListener("input", debounce(renameMakeImageButton, 1000)) - -pauseBtn.addEventListener("click", function () { +pauseBtn.addEventListener("click", function() { pauseClient = true - pauseBtn.style.display="none" + pauseBtn.style.display = "none" resumeBtn.style.display = "inline" - document.body.classList.add('wait-pause') + document.body.classList.add("wait-pause") }) -resumeBtn.addEventListener("click", function () { +resumeBtn.addEventListener("click", function() { pauseClient = false resumeBtn.style.display = "none" pauseBtn.style.display = "inline" - document.body.classList.remove('pause') - document.body.classList.remove('wait-pause') + document.body.classList.remove("pause") + document.body.classList.remove("wait-pause") }) /* Pause function */ document.querySelectorAll(".tab").forEach(linkTabContents) window.addEventListener("beforeunload", function(e) { - const msg = "Unsaved pictures will be lost!"; + const msg = "Unsaved pictures will be lost!" - let elementList = document.getElementsByClassName("imageTaskContainer"); + let elementList = document.getElementsByClassName("imageTaskContainer") if (elementList.length != 0) { - e.preventDefault(); - (e || window.event).returnValue = msg; - return msg; + e.preventDefault() + ;(e || window.event).returnValue = msg + return msg } else { - return true; + return true } -}); +}) createCollapsibles() -prettifyInputs(document); +prettifyInputs(document) // set the textbox as focused on start promptField.focus() diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index af399205..7a263315 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -8,8 +8,8 @@ var ParameterType = { select: "select", select_multiple: "select_multiple", slider: "slider", - custom: "custom", -}; + custom: "custom" +} /** * JSDoc style @@ -24,7 +24,6 @@ var ParameterType = { * @property {boolean?} saveInAppConfig */ - /** @type {Array.} */ var PARAMETERS = [ { @@ -33,7 +32,8 @@ var PARAMETERS = [ label: "Theme", default: "theme-default", note: "customize the look and feel of the ui", - options: [ // Note: options expanded dynamically + options: [ + // Note: options expanded dynamically { value: "theme-default", label: "Default" @@ -47,7 +47,7 @@ var PARAMETERS = [ label: "Auto-Save Images", note: "automatically saves images to the specified location", icon: "fa-download", - default: false, + default: false }, { id: "diskPath", @@ -82,13 +82,13 @@ var PARAMETERS = [ }, { value: "embed,txt", - label: "embed & txt", + label: "embed & txt" }, { value: "embed,json", - label: "embed & json", - }, - ], + label: "embed & json" + } + ] }, { id: "block_nsfw", @@ -96,7 +96,7 @@ var PARAMETERS = [ label: "Block NSFW images", note: "blurs out NSFW images", icon: "fa-land-mine-on", - default: false, + default: false }, { id: "sound_toggle", @@ -104,7 +104,7 @@ var PARAMETERS = [ label: "Enable Sound", note: "plays a sound on task completion", icon: "fa-volume-low", - default: true, + default: true }, { id: "process_order_toggle", @@ -112,7 +112,7 @@ var PARAMETERS = [ label: "Process newest jobs first", note: "reverse the normal processing order", icon: "fa-arrow-down-short-wide", - default: false, + default: false }, { id: "ui_open_browser_on_start", @@ -121,23 +121,24 @@ var PARAMETERS = [ note: "starts the default browser on startup", icon: "fa-window-restore", default: true, - saveInAppConfig: true, + saveInAppConfig: true }, { id: "vram_usage_level", type: ParameterType.select, label: "GPU Memory Usage", - note: "Faster performance requires more GPU memory (VRAM)

" + - "Balanced: nearly as fast as High, much lower VRAM usage
" + - "High: fastest, maximum GPU memory usage
" + - "Low: slowest, recommended for GPUs with 3 to 4 GB memory", + note: + "Faster performance requires more GPU memory (VRAM)

" + + "Balanced: nearly as fast as High, much lower VRAM usage
" + + "High: fastest, maximum GPU memory usage
" + + "Low: slowest, recommended for GPUs with 3 to 4 GB memory", icon: "fa-forward", default: "balanced", options: [ - {value: "balanced", label: "Balanced"}, - {value: "high", label: "High"}, - {value: "low", label: "Low"} - ], + { value: "balanced", label: "Balanced" }, + { value: "high", label: "High" }, + { value: "low", label: "Low" } + ] }, { id: "use_cpu", @@ -145,20 +146,20 @@ var PARAMETERS = [ label: "Use CPU (not GPU)", note: "warning: this will be *very* slow", icon: "fa-microchip", - default: false, + default: false }, { id: "auto_pick_gpus", type: ParameterType.checkbox, label: "Automatically pick the GPUs (experimental)", - default: false, + default: false }, { id: "use_gpus", type: ParameterType.select_multiple, label: "GPUs to use (experimental)", note: "to process in parallel", - default: false, + default: false }, { id: "auto_save_settings", @@ -166,15 +167,16 @@ var PARAMETERS = [ label: "Auto-Save Settings", note: "restores settings on browser load", icon: "fa-gear", - default: true, + default: true }, { id: "confirm_dangerous_actions", type: ParameterType.checkbox, label: "Confirm dangerous actions", - note: "Actions that might lead to data loss must either be clicked with the shift key pressed, or confirmed in an 'Are you sure?' dialog", + note: + "Actions that might lead to data loss must either be clicked with the shift key pressed, or confirmed in an 'Are you sure?' dialog", icon: "fa-check-double", - default: true, + default: true }, { id: "listen_to_network", @@ -183,7 +185,7 @@ var PARAMETERS = [ note: "Other devices on your network can access this web page", icon: "fa-network-wired", default: true, - saveInAppConfig: true, + saveInAppConfig: true }, { id: "listen_port", @@ -194,29 +196,31 @@ var PARAMETERS = [ render: (parameter) => { return `` }, - saveInAppConfig: true, + saveInAppConfig: true }, { id: "use_beta_channel", type: ParameterType.checkbox, label: "Beta channel", - note: "Get the latest features immediately (but could be less stable). Please restart the program after changing this.", + note: + "Get the latest features immediately (but could be less stable). Please restart the program after changing this.", icon: "fa-fire", - default: false, + default: false }, { id: "test_diffusers", type: ParameterType.checkbox, label: "Test Diffusers", - note: "Experimental! Can have bugs! Use upcoming features (like LoRA) in our new engine. Please press Save, then restart the program after changing this.", + note: + "Experimental! Can have bugs! Use upcoming features (like LoRA) in our new engine. Please press Save, then restart the program after changing this.", icon: "fa-bolt", default: false, - saveInAppConfig: true, - }, -]; + saveInAppConfig: true + } +] function getParameterSettingsEntry(id) { - let parameter = PARAMETERS.filter(p => p.id === id) + let parameter = PARAMETERS.filter((p) => p.id === id) if (parameter.length === 0) { return } @@ -224,37 +228,39 @@ function getParameterSettingsEntry(id) { } function sliderUpdate(event) { - if (event.srcElement.id.endsWith('-input')) { - let slider = document.getElementById(event.srcElement.id.slice(0,-6)) + if (event.srcElement.id.endsWith("-input")) { + let slider = document.getElementById(event.srcElement.id.slice(0, -6)) slider.value = event.srcElement.value slider.dispatchEvent(new Event("change")) } else { - let field = document.getElementById(event.srcElement.id+'-input') + let field = document.getElementById(event.srcElement.id + "-input") field.value = event.srcElement.value field.dispatchEvent(new Event("change")) } } /** - * @param {Parameter} parameter + * @param {Parameter} parameter * @returns {string | HTMLElement} */ function getParameterElement(parameter) { switch (parameter.type) { case ParameterType.checkbox: - var is_checked = parameter.default ? " checked" : ""; + var is_checked = parameter.default ? " checked" : "" return `` case ParameterType.select: case ParameterType.select_multiple: - var options = (parameter.options || []).map(option => ``).join("") - var multiple = (parameter.type == ParameterType.select_multiple ? 'multiple' : '') + var options = (parameter.options || []) + .map((option) => ``) + .join("") + var multiple = parameter.type == ParameterType.select_multiple ? "multiple" : "" return `` case ParameterType.slider: return `  ${parameter.slider_unit}` case ParameterType.custom: return parameter.render(parameter) default: - console.error(`Invalid type ${parameter.type} for parameter ${parameter.id}`); + console.error(`Invalid type ${parameter.type} for parameter ${parameter.id}`) return "ERROR: Invalid Type" } } @@ -265,31 +271,31 @@ let parametersTable = document.querySelector("#system-settings .parameters-table * @param {Array | undefined} parameters * */ function initParameters(parameters) { - parameters.forEach(parameter => { + parameters.forEach((parameter) => { const element = getParameterElement(parameter) - const elementWrapper = createElement('div') + const elementWrapper = createElement("div") if (element instanceof Node) { elementWrapper.appendChild(element) } else { elementWrapper.innerHTML = element } - const note = typeof parameter.note === 'function' ? parameter.note(parameter) : parameter.note + const note = typeof parameter.note === "function" ? parameter.note(parameter) : parameter.note const noteElements = [] if (note) { - const noteElement = createElement('small') + const noteElement = createElement("small") if (note instanceof Node) { noteElement.appendChild(note) } else { - noteElement.innerHTML = note || '' + noteElement.innerHTML = note || "" } noteElements.push(noteElement) } - const icon = parameter.icon ? [createElement('i', undefined, ['fa', parameter.icon])] : [] + const icon = parameter.icon ? [createElement("i", undefined, ["fa", parameter.icon])] : [] - const label = typeof parameter.label === 'function' ? parameter.label(parameter) : parameter.label - const labelElement = createElement('label', { for: parameter.id }) + const label = typeof parameter.label === "function" ? parameter.label(parameter) : parameter.label + const labelElement = createElement("label", { for: parameter.id }) if (label instanceof Node) { labelElement.appendChild(label) } else { @@ -297,13 +303,13 @@ function initParameters(parameters) { } const newrow = createElement( - 'div', - { 'data-setting-id': parameter.id, 'data-save-in-app-config': parameter.saveInAppConfig }, + "div", + { "data-setting-id": parameter.id, "data-save-in-app-config": parameter.saveInAppConfig }, undefined, [ - createElement('div', undefined, undefined, icon), - createElement('div', undefined, undefined, [labelElement, ...noteElements]), - elementWrapper, + createElement("div", undefined, undefined, icon), + createElement("div", undefined, undefined, [labelElement, ...noteElements]), + elementWrapper ] ) parametersTable.appendChild(newrow) @@ -314,22 +320,25 @@ function initParameters(parameters) { initParameters(PARAMETERS) // listen to parameters from plugins -PARAMETERS.addEventListener('push', (...items) => { +PARAMETERS.addEventListener("push", (...items) => { initParameters(items) - - if (items.find(item => item.saveInAppConfig)) { - console.log('Reloading app config for new parameters', items.map(p => p.id)) + + if (items.find((item) => item.saveInAppConfig)) { + console.log( + "Reloading app config for new parameters", + items.map((p) => p.id) + ) getAppConfig() } }) -let vramUsageLevelField = document.querySelector('#vram_usage_level') -let useCPUField = document.querySelector('#use_cpu') -let autoPickGPUsField = document.querySelector('#auto_pick_gpus') -let useGPUsField = document.querySelector('#use_gpus') -let saveToDiskField = document.querySelector('#save_to_disk') -let diskPathField = document.querySelector('#diskPath') -let metadataOutputFormatField = document.querySelector('#metadata_output_format') +let vramUsageLevelField = document.querySelector("#vram_usage_level") +let useCPUField = document.querySelector("#use_cpu") +let autoPickGPUsField = document.querySelector("#auto_pick_gpus") +let useGPUsField = document.querySelector("#use_gpus") +let saveToDiskField = document.querySelector("#save_to_disk") +let diskPathField = document.querySelector("#diskPath") +let metadataOutputFormatField = document.querySelector("#metadata_output_format") let listenToNetworkField = document.querySelector("#listen_to_network") let listenPortField = document.querySelector("#listen_port") let useBetaChannelField = document.querySelector("#use_beta_channel") @@ -337,35 +346,34 @@ let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_star let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions") let testDiffusers = document.querySelector("#test_diffusers") -let saveSettingsBtn = document.querySelector('#save-system-settings-btn') - +let saveSettingsBtn = document.querySelector("#save-system-settings-btn") async function changeAppConfig(configDelta) { try { - let res = await fetch('/app_config', { - method: 'POST', + let res = await fetch("/app_config", { + method: "POST", headers: { - 'Content-Type': 'application/json' + "Content-Type": "application/json" }, body: JSON.stringify(configDelta) }) res = await res.json() - console.log('set config status response', res) + console.log("set config status response", res) } catch (e) { - console.log('set config status error', e) + console.log("set config status error", e) } } async function getAppConfig() { try { - let res = await fetch('/get/app_config') + let res = await fetch("/get/app_config") const config = await res.json() applySettingsFromConfig(config) // custom overrides - if (config.update_branch === 'beta') { + if (config.update_branch === "beta") { useBetaChannelField.checked = true document.querySelector("#updateBranchLabel").innerText = "(beta)" } else { @@ -380,45 +388,48 @@ async function getAppConfig() { if (config.net && config.net.listen_port !== undefined) { listenPortField.value = config.net.listen_port } - if (config.test_diffusers === undefined || config.update_branch === 'main') { + if (config.test_diffusers === undefined || config.update_branch === "main") { testDiffusers.checked = false - document.querySelector("#lora_model_container").style.display = 'none' - document.querySelector("#lora_alpha_container").style.display = 'none' + document.querySelector("#lora_model_container").style.display = "none" + document.querySelector("#lora_alpha_container").style.display = "none" } else { - testDiffusers.checked = config.test_diffusers && config.update_branch !== 'main' - document.querySelector("#lora_model_container").style.display = (testDiffusers.checked ? '' : 'none') - document.querySelector("#lora_alpha_container").style.display = (testDiffusers.checked && loraModelField.value !== "" ? '' : 'none') + testDiffusers.checked = config.test_diffusers && config.update_branch !== "main" + document.querySelector("#lora_model_container").style.display = testDiffusers.checked ? "" : "none" + document.querySelector("#lora_alpha_container").style.display = + testDiffusers.checked && loraModelField.value !== "" ? "" : "none" } - console.log('get config status response', config) + console.log("get config status response", config) return config } catch (e) { - console.log('get config status error', e) + console.log("get config status error", e) return {} } } function applySettingsFromConfig(config) { - Array.from(parametersTable.children).forEach(parameterRow => { - if (parameterRow.dataset.settingId in config && parameterRow.dataset.saveInAppConfig === 'true') { + Array.from(parametersTable.children).forEach((parameterRow) => { + if (parameterRow.dataset.settingId in config && parameterRow.dataset.saveInAppConfig === "true") { const configValue = config[parameterRow.dataset.settingId] - const parameterElement = document.getElementById(parameterRow.dataset.settingId) || - parameterRow.querySelector('input') || parameterRow.querySelector('select') + const parameterElement = + document.getElementById(parameterRow.dataset.settingId) || + parameterRow.querySelector("input") || + parameterRow.querySelector("select") switch (parameterElement?.tagName) { - case 'INPUT': - if (parameterElement.type === 'checkbox') { + case "INPUT": + if (parameterElement.type === "checkbox") { parameterElement.checked = configValue } else { parameterElement.value = configValue } - parameterElement.dispatchEvent(new Event('change')) + parameterElement.dispatchEvent(new Event("change")) break - case 'SELECT': + case "SELECT": if (Array.isArray(configValue)) { - Array.from(parameterElement.options).forEach(option => { + Array.from(parameterElement.options).forEach((option) => { if (configValue.includes(option.value || option.text)) { option.selected = true } @@ -426,82 +437,85 @@ function applySettingsFromConfig(config) { } else { parameterElement.value = configValue } - parameterElement.dispatchEvent(new Event('change')) + parameterElement.dispatchEvent(new Event("change")) break } } }) } -saveToDiskField.addEventListener('change', function(e) { +saveToDiskField.addEventListener("change", function(e) { diskPathField.disabled = !this.checked metadataOutputFormatField.disabled = !this.checked }) function getCurrentRenderDeviceSelection() { - let selectedGPUs = $('#use_gpus').val() + let selectedGPUs = $("#use_gpus").val() if (useCPUField.checked && !autoPickGPUsField.checked) { - return 'cpu' + return "cpu" } if (autoPickGPUsField.checked || selectedGPUs.length == 0) { - return 'auto' + return "auto" } - return selectedGPUs.join(',') + return selectedGPUs.join(",") } -useCPUField.addEventListener('click', function() { - let gpuSettingEntry = getParameterSettingsEntry('use_gpus') - let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus') +useCPUField.addEventListener("click", function() { + let gpuSettingEntry = getParameterSettingsEntry("use_gpus") + let autoPickGPUSettingEntry = getParameterSettingsEntry("auto_pick_gpus") if (this.checked) { - gpuSettingEntry.style.display = 'none' - autoPickGPUSettingEntry.style.display = 'none' - autoPickGPUsField.setAttribute('data-old-value', autoPickGPUsField.checked) + gpuSettingEntry.style.display = "none" + autoPickGPUSettingEntry.style.display = "none" + autoPickGPUsField.setAttribute("data-old-value", autoPickGPUsField.checked) autoPickGPUsField.checked = false } else if (useGPUsField.options.length >= MIN_GPUS_TO_SHOW_SELECTION) { - gpuSettingEntry.style.display = '' - autoPickGPUSettingEntry.style.display = '' - let oldVal = autoPickGPUsField.getAttribute('data-old-value') - if (oldVal === null || oldVal === undefined) { // the UI started with CPU selected by default + gpuSettingEntry.style.display = "" + autoPickGPUSettingEntry.style.display = "" + let oldVal = autoPickGPUsField.getAttribute("data-old-value") + if (oldVal === null || oldVal === undefined) { + // the UI started with CPU selected by default autoPickGPUsField.checked = true } else { - autoPickGPUsField.checked = (oldVal === 'true') + autoPickGPUsField.checked = oldVal === "true" } - gpuSettingEntry.style.display = (autoPickGPUsField.checked ? 'none' : '') + gpuSettingEntry.style.display = autoPickGPUsField.checked ? "none" : "" } }) -useGPUsField.addEventListener('click', function() { - let selectedGPUs = $('#use_gpus').val() - autoPickGPUsField.checked = (selectedGPUs.length === 0) +useGPUsField.addEventListener("click", function() { + let selectedGPUs = $("#use_gpus").val() + autoPickGPUsField.checked = selectedGPUs.length === 0 }) -autoPickGPUsField.addEventListener('click', function() { +autoPickGPUsField.addEventListener("click", function() { if (this.checked) { - $('#use_gpus').val([]) + $("#use_gpus").val([]) } - let gpuSettingEntry = getParameterSettingsEntry('use_gpus') - gpuSettingEntry.style.display = (this.checked ? 'none' : '') + let gpuSettingEntry = getParameterSettingsEntry("use_gpus") + gpuSettingEntry.style.display = this.checked ? "none" : "" }) -async function setDiskPath(defaultDiskPath, force=false) { +async function setDiskPath(defaultDiskPath, force = false) { var diskPath = getSetting("diskPath") - if (force || diskPath == '' || diskPath == undefined || diskPath == "undefined") { + if (force || diskPath == "" || diskPath == undefined || diskPath == "undefined") { setSetting("diskPath", defaultDiskPath) } } function setDeviceInfo(devices) { let cpu = devices.all.cpu.name - let allGPUs = Object.keys(devices.all).filter(d => d != 'cpu') + let allGPUs = Object.keys(devices.all).filter((d) => d != "cpu") let activeGPUs = Object.keys(devices.active) function ID_TO_TEXT(d) { let info = devices.all[d] if ("mem_free" in info && "mem_total" in info) { - return `${info.name} (${d}) (${info.mem_free.toFixed(1)}Gb free / ${info.mem_total.toFixed(1)} Gb total)` + return `${info.name} (${d}) (${info.mem_free.toFixed(1)}Gb free / ${info.mem_total.toFixed( + 1 + )} Gb total)` } else { return `${info.name} (${d}) (no memory info)` } @@ -510,35 +524,35 @@ function setDeviceInfo(devices) { allGPUs = allGPUs.map(ID_TO_TEXT) activeGPUs = activeGPUs.map(ID_TO_TEXT) - let systemInfoEl = document.querySelector('#system-info') - systemInfoEl.querySelector('#system-info-cpu').innerText = cpu - systemInfoEl.querySelector('#system-info-gpus-all').innerHTML = allGPUs.join('
') - systemInfoEl.querySelector('#system-info-rendering-devices').innerHTML = activeGPUs.join('
') + let systemInfoEl = document.querySelector("#system-info") + systemInfoEl.querySelector("#system-info-cpu").innerText = cpu + systemInfoEl.querySelector("#system-info-gpus-all").innerHTML = allGPUs.join("
") + systemInfoEl.querySelector("#system-info-rendering-devices").innerHTML = activeGPUs.join("
") } function setHostInfo(hosts) { let port = listenPortField.value - hosts = hosts.map(addr => `http://${addr}:${port}/`).map(url => ``) - document.querySelector('#system-info-server-hosts').innerHTML = hosts.join('') + hosts = hosts.map((addr) => `http://${addr}:${port}/`).map((url) => ``) + document.querySelector("#system-info-server-hosts").innerHTML = hosts.join("") } async function getSystemInfo() { try { const res = await SD.getSystemInfo() - let devices = res['devices'] + let devices = res["devices"] - let allDeviceIds = Object.keys(devices['all']).filter(d => d !== 'cpu') - let activeDeviceIds = Object.keys(devices['active']).filter(d => d !== 'cpu') + let allDeviceIds = Object.keys(devices["all"]).filter((d) => d !== "cpu") + let activeDeviceIds = Object.keys(devices["active"]).filter((d) => d !== "cpu") if (activeDeviceIds.length === 0) { useCPUField.checked = true } if (allDeviceIds.length < MIN_GPUS_TO_SHOW_SELECTION || useCPUField.checked) { - let gpuSettingEntry = getParameterSettingsEntry('use_gpus') - gpuSettingEntry.style.display = 'none' - let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus') - autoPickGPUSettingEntry.style.display = 'none' + let gpuSettingEntry = getParameterSettingsEntry("use_gpus") + gpuSettingEntry.style.display = "none" + let autoPickGPUSettingEntry = getParameterSettingsEntry("auto_pick_gpus") + autoPickGPUSettingEntry.style.display = "none" } if (allDeviceIds.length === 0) { @@ -546,86 +560,90 @@ async function getSystemInfo() { useCPUField.disabled = true // no compatible GPUs, so make the CPU mandatory } - autoPickGPUsField.checked = (devices['config'] === 'auto') + autoPickGPUsField.checked = devices["config"] === "auto" - useGPUsField.innerHTML = '' - allDeviceIds.forEach(device => { - let deviceName = devices['all'][device]['name'] + useGPUsField.innerHTML = "" + allDeviceIds.forEach((device) => { + let deviceName = devices["all"][device]["name"] let deviceOption = `` - useGPUsField.insertAdjacentHTML('beforeend', deviceOption) + useGPUsField.insertAdjacentHTML("beforeend", deviceOption) }) if (autoPickGPUsField.checked) { - let gpuSettingEntry = getParameterSettingsEntry('use_gpus') - gpuSettingEntry.style.display = 'none' + let gpuSettingEntry = getParameterSettingsEntry("use_gpus") + gpuSettingEntry.style.display = "none" } else { - $('#use_gpus').val(activeDeviceIds) + $("#use_gpus").val(activeDeviceIds) } setDeviceInfo(devices) - setHostInfo(res['hosts']) + setHostInfo(res["hosts"]) let force = false - if (res['enforce_output_dir'] !== undefined) { - force = res['enforce_output_dir'] + if (res["enforce_output_dir"] !== undefined) { + force = res["enforce_output_dir"] if (force == true) { - saveToDiskField.checked = true - metadataOutputFormatField.disabled = false + saveToDiskField.checked = true + metadataOutputFormatField.disabled = false } saveToDiskField.disabled = force diskPathField.disabled = force } - setDiskPath(res['default_output_dir'], force) + setDiskPath(res["default_output_dir"], force) } catch (e) { - console.log('error fetching devices', e) + console.log("error fetching devices", e) } } -saveSettingsBtn.addEventListener('click', function() { - if (listenPortField.value == '') { - alert('The network port field must not be empty.') +saveSettingsBtn.addEventListener("click", function() { + if (listenPortField.value == "") { + alert("The network port field must not be empty.") return } if (listenPortField.value < 1 || listenPortField.value > 65535) { - alert('The network port must be a number from 1 to 65535') + alert("The network port must be a number from 1 to 65535") return } - const updateBranch = (useBetaChannelField.checked ? 'beta' : 'main') + const updateBranch = useBetaChannelField.checked ? "beta" : "main" const updateAppConfigRequest = { - 'render_devices': getCurrentRenderDeviceSelection(), - 'update_branch': updateBranch, + render_devices: getCurrentRenderDeviceSelection(), + update_branch: updateBranch } - Array.from(parametersTable.children).forEach(parameterRow => { - if (parameterRow.dataset.saveInAppConfig === 'true') { - const parameterElement = document.getElementById(parameterRow.dataset.settingId) || - parameterRow.querySelector('input') || parameterRow.querySelector('select') + Array.from(parametersTable.children).forEach((parameterRow) => { + if (parameterRow.dataset.saveInAppConfig === "true") { + const parameterElement = + document.getElementById(parameterRow.dataset.settingId) || + parameterRow.querySelector("input") || + parameterRow.querySelector("select") switch (parameterElement?.tagName) { - case 'INPUT': - if (parameterElement.type === 'checkbox') { + case "INPUT": + if (parameterElement.type === "checkbox") { updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.checked } else { updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value } break - case 'SELECT': + case "SELECT": if (parameterElement.multiple) { updateAppConfigRequest[parameterRow.dataset.settingId] = Array.from(parameterElement.options) - .filter(option => option.selected) - .map(option => option.value || option.text) + .filter((option) => option.selected) + .map((option) => option.value || option.text) } else { updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value } break default: - console.error(`Setting parameter ${parameterRow.dataset.settingId} couldn't be saved to app.config - element #${parameter.id} is a <${parameterElement?.tagName} /> instead of a or a or a