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 = `${setting.label} (${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(`Use as Input
`)
- $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 = `