Merge pull request #1219 from lucasmarcelli/prettier-beta-take-two

add prettier for JS style
This commit is contained in:
cmdr2 2023-04-28 15:16:32 +05:30 committed by GitHub
commit af0058d2aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 13142 additions and 13604 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ installer
installer.tar installer.tar
dist dist
.idea/* .idea/*
node_modules/*

8
.prettierignore Normal file
View File

@ -0,0 +1,8 @@
*.min.*
*.py
/*
!/ui
/ui/easydiffusion
/ui/hotfix
!/ui/plugins
!/ui/media

7
.prettierrc.json Normal file
View File

@ -0,0 +1,7 @@
{
"printWidth": 120,
"tabWidth": 4,
"semi": false,
"arrowParens": "always",
"trailingComma": "none"
}

8
package.json Normal file
View File

@ -0,0 +1,8 @@
{
"scripts": {
"prettier": "prettier --write \"./**/*.js\""
},
"devDependencies": {
"prettier": "^1.19.1"
}
}

View File

@ -55,11 +55,10 @@ const SETTINGS_IDS_LIST = [
"json_toggle" "json_toggle"
] ]
const IGNORE_BY_DEFAULT = [ const IGNORE_BY_DEFAULT = ["prompt"]
"prompt"
]
const SETTINGS_SECTIONS = [ // gets the "keys" property filled in with an ordered list of settings in this section via initSettings 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-inputs", name: "Prompt" },
{ id: "editor-settings", name: "Image Settings" }, { id: "editor-settings", name: "Image Settings" },
{ id: "system-settings", name: "System Settings" }, { id: "system-settings", name: "System Settings" },
@ -67,12 +66,13 @@ const SETTINGS_SECTIONS = [ // gets the "keys" property filled in with an ordere
] ]
async function initSettings() { async function initSettings() {
SETTINGS_IDS_LIST.forEach(id => { SETTINGS_IDS_LIST.forEach((id) => {
var element = document.getElementById(id) var element = document.getElementById(id)
if (!element) { if (!element) {
console.error(`Missing settings element ${id}`) 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 return
} }
SETTINGS[id] = { SETTINGS[id] = {
@ -87,22 +87,22 @@ async function initSettings() {
element.addEventListener("change", settingChangeHandler) element.addEventListener("change", settingChangeHandler)
}) })
var unsorted_settings_ids = [...SETTINGS_IDS_LIST] var unsorted_settings_ids = [...SETTINGS_IDS_LIST]
SETTINGS_SECTIONS.forEach(section => { SETTINGS_SECTIONS.forEach((section) => {
var name = section.name var name = section.name
var element = document.getElementById(section.id) var element = document.getElementById(section.id)
var unsorted_ids = unsorted_settings_ids.map(id => `#${id}`).join(",") var unsorted_ids = unsorted_settings_ids.map((id) => `#${id}`).join(",")
var children = unsorted_ids == "" ? [] : Array.from(element.querySelectorAll(unsorted_ids)); var children = unsorted_ids == "" ? [] : Array.from(element.querySelectorAll(unsorted_ids))
section.keys = [] section.keys = []
children.forEach(e => { children.forEach((e) => {
section.keys.push(e.id) 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() loadSettings()
} }
function getSetting(element) { function getSetting(element) {
if (element.dataset && 'path' in element.dataset) { if (element.dataset && "path" in element.dataset) {
return element.dataset.path return element.dataset.path
} }
if (typeof element === "string" || element instanceof String) { if (typeof element === "string" || element instanceof String) {
@ -114,7 +114,7 @@ function getSetting(element) {
return element.value return element.value
} }
function setSetting(element, value) { function setSetting(element, value) {
if (element.dataset && 'path' in element.dataset) { if (element.dataset && "path" in element.dataset) {
element.dataset.path = value element.dataset.path = value
return // no need to dispatch any event here because the models are not loaded yet 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") { if (element.type == "checkbox") {
element.checked = value element.checked = value
} } else {
else {
element.value = value element.value = value
} }
element.dispatchEvent(new Event("input")) element.dispatchEvent(new Event("input"))
@ -136,7 +135,7 @@ function setSetting(element, value) {
} }
function saveSettings() { function saveSettings() {
var saved_settings = Object.values(SETTINGS).map(setting => { var saved_settings = Object.values(SETTINGS).map((setting) => {
return { return {
key: setting.key, key: setting.key,
value: setting.value, value: setting.value,
@ -151,16 +150,16 @@ function loadSettings() {
var saved_settings_text = localStorage.getItem(SETTINGS_KEY) var saved_settings_text = localStorage.getItem(SETTINGS_KEY)
if (saved_settings_text) { if (saved_settings_text) {
var saved_settings = JSON.parse(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) setSetting("auto_save_settings", false)
return return
} }
CURRENTLY_LOADING_SETTINGS = true CURRENTLY_LOADING_SETTINGS = true
saved_settings.forEach(saved_setting => { saved_settings.forEach((saved_setting) => {
var setting = SETTINGS[saved_setting.key] var setting = SETTINGS[saved_setting.key]
if (!setting) { if (!setting) {
console.warn(`Attempted to load setting ${saved_setting.key}, but no setting found`); console.warn(`Attempted to load setting ${saved_setting.key}, but no setting found`)
return null; return null
} }
setting.ignore = saved_setting.ignore setting.ignore = saved_setting.ignore
if (!setting.ignore) { if (!setting.ignore) {
@ -169,10 +168,9 @@ function loadSettings() {
} }
}) })
CURRENTLY_LOADING_SETTINGS = false CURRENTLY_LOADING_SETTINGS = false
} } else {
else {
CURRENTLY_LOADING_SETTINGS = true CURRENTLY_LOADING_SETTINGS = true
tryLoadOldSettings(); tryLoadOldSettings()
CURRENTLY_LOADING_SETTINGS = false CURRENTLY_LOADING_SETTINGS = false
saveSettings() saveSettings()
} }
@ -180,9 +178,9 @@ function loadSettings() {
function loadDefaultSettingsSection(section_id) { function loadDefaultSettingsSection(section_id) {
CURRENTLY_LOADING_SETTINGS = true CURRENTLY_LOADING_SETTINGS = true
var section = SETTINGS_SECTIONS.find(s => s.id == section_id); var section = SETTINGS_SECTIONS.find((s) => s.id == section_id)
section.keys.forEach(key => { section.keys.forEach((key) => {
var setting = SETTINGS[key]; var setting = SETTINGS[key]
setting.value = setting.default setting.value = setting.default
setSetting(setting.element, setting.value) setSetting(setting.element, setting.value)
}) })
@ -218,10 +216,10 @@ function getSettingLabel(element) {
function fillSaveSettingsConfigTable() { function fillSaveSettingsConfigTable() {
saveSettingsConfigTable.textContent = "" saveSettingsConfigTable.textContent = ""
SETTINGS_SECTIONS.forEach(section => { SETTINGS_SECTIONS.forEach((section) => {
var section_row = `<tr><th>${section.name}</th><td></td></tr>` var section_row = `<tr><th>${section.name}</th><td></td></tr>`
saveSettingsConfigTable.insertAdjacentHTML("beforeend", section_row) saveSettingsConfigTable.insertAdjacentHTML("beforeend", section_row)
section.keys.forEach(key => { section.keys.forEach((key) => {
var setting = SETTINGS[key] var setting = SETTINGS[key]
var element = setting.element var element = setting.element
var checkbox_id = `shouldsave_${element.id}` var checkbox_id = `shouldsave_${element.id}`
@ -234,7 +232,7 @@ function fillSaveSettingsConfigTable() {
var newrow = `<tr><td><label for="${checkbox_id}">${setting.label}</label></td><td><input id="${checkbox_id}" name="${checkbox_id}" ${is_checked} type="checkbox" ></td><td><small>(${value})</small></td></tr>` var newrow = `<tr><td><label for="${checkbox_id}">${setting.label}</label></td><td><input id="${checkbox_id}" name="${checkbox_id}" ${is_checked} type="checkbox" ></td><td><small>(${value})</small></td></tr>`
saveSettingsConfigTable.insertAdjacentHTML("beforeend", newrow) saveSettingsConfigTable.insertAdjacentHTML("beforeend", newrow)
var checkbox = document.getElementById(checkbox_id) var checkbox = document.getElementById(checkbox_id)
checkbox.addEventListener("input", event => { checkbox.addEventListener("input", (event) => {
setting.ignore = !checkbox.checked setting.ignore = !checkbox.checked
saveSettings() saveSettings()
}) })
@ -245,9 +243,6 @@ function fillSaveSettingsConfigTable() {
// configureSettingsSaveBtn // configureSettingsSaveBtn
var autoSaveSettings = document.getElementById("auto_save_settings") var autoSaveSettings = document.getElementById("auto_save_settings")
var configSettingsButton = document.createElement("button") var configSettingsButton = document.createElement("button")
configSettingsButton.textContent = "Configure" configSettingsButton.textContent = "Configure"
@ -256,33 +251,32 @@ autoSaveSettings.insertAdjacentElement("beforebegin", configSettingsButton)
autoSaveSettings.addEventListener("change", () => { autoSaveSettings.addEventListener("change", () => {
configSettingsButton.style.display = autoSaveSettings.checked ? "block" : "none" configSettingsButton.style.display = autoSaveSettings.checked ? "block" : "none"
}) })
configSettingsButton.addEventListener('click', () => { configSettingsButton.addEventListener("click", () => {
fillSaveSettingsConfigTable() fillSaveSettingsConfigTable()
saveSettingsConfigOverlay.classList.add("active") saveSettingsConfigOverlay.classList.add("active")
}) })
resetImageSettingsButton.addEventListener('click', event => { resetImageSettingsButton.addEventListener("click", (event) => {
loadDefaultSettingsSection("editor-settings"); loadDefaultSettingsSection("editor-settings")
event.stopPropagation() event.stopPropagation()
}) })
function tryLoadOldSettings() { function tryLoadOldSettings() {
console.log("Loading old user settings") console.log("Loading old user settings")
// load v1 auto-save.js settings // load v1 auto-save.js settings
var old_map = { var old_map = {
"guidance_scale_slider": "guidance_scale", guidance_scale_slider: "guidance_scale",
"prompt_strength_slider": "prompt_strength" prompt_strength_slider: "prompt_strength"
} }
var settings_key_v1 = "user_settings" var settings_key_v1 = "user_settings"
var saved_settings_text = localStorage.getItem(settings_key_v1) var saved_settings_text = localStorage.getItem(settings_key_v1)
if (saved_settings_text) { if (saved_settings_text) {
var saved_settings = JSON.parse(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 key = key in old_map ? old_map[key] : key
if (!(key in SETTINGS)) return if (!(key in SETTINGS)) return
SETTINGS[key].ignore = !saved_settings.should_save[key] 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 key = key in old_map ? old_map[key] : key
if (!(key in SETTINGS)) return if (!(key in SETTINGS)) return
var setting = SETTINGS[key] var setting = SETTINGS[key]
@ -290,38 +284,42 @@ function tryLoadOldSettings() {
setting.value = saved_settings.values[key] setting.value = saved_settings.values[key]
setSetting(setting.element, setting.value) setSetting(setting.element, setting.value)
} }
}); })
localStorage.removeItem(settings_key_v1) localStorage.removeItem(settings_key_v1)
} }
// load old individually stored items // load old individually stored items
var individual_settings_map = { // maps old localStorage-key to new SETTINGS-key var individual_settings_map = {
"soundEnabled": "sound_toggle", // maps old localStorage-key to new SETTINGS-key
"saveToDisk": "save_to_disk", soundEnabled: "sound_toggle",
"useCPU": "use_cpu", saveToDisk: "save_to_disk",
"diskPath": "diskPath", useCPU: "use_cpu",
"useFaceCorrection": "use_face_correction", diskPath: "diskPath",
"useUpscaling": "use_upscale", useFaceCorrection: "use_face_correction",
"showOnlyFilteredImage": "show_only_filtered_image", useUpscaling: "use_upscale",
"streamImageProgress": "stream_image_progress", showOnlyFilteredImage: "show_only_filtered_image",
"outputFormat": "output_format", streamImageProgress: "stream_image_progress",
"autoSaveSettings": "auto_save_settings", outputFormat: "output_format",
}; autoSaveSettings: "auto_save_settings"
Object.keys(individual_settings_map).forEach(localStorageKey => { }
var localStorageValue = localStorage.getItem(localStorageKey); Object.keys(individual_settings_map).forEach((localStorageKey) => {
var localStorageValue = localStorage.getItem(localStorageKey)
if (localStorageValue !== null) { if (localStorageValue !== null) {
let key = individual_settings_map[localStorageKey] let key = individual_settings_map[localStorageKey]
var setting = SETTINGS[key] var setting = SETTINGS[key]
if (!setting) { if (!setting) {
console.warn(`Attempted to map old setting ${key}, but no setting found`); console.warn(`Attempted to map old setting ${key}, but no setting found`)
return null; 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" localStorageValue = localStorageValue == "true"
} }
setting.value = localStorageValue setting.value = localStorageValue
setSetting(setting.element, setting.value) setSetting(setting.element, setting.value)
localStorage.removeItem(localStorageKey); localStorage.removeItem(localStorageKey)
} }
}) })
} }

View File

@ -1,17 +1,17 @@
"use strict" // Opt in to a restricted variant of JavaScript "use strict" // Opt in to a restricted variant of JavaScript
const EXT_REGEX = /(?:\.([^.]+))?$/ const EXT_REGEX = /(?:\.([^.]+))?$/
const TEXT_EXTENSIONS = ['txt', 'json'] const TEXT_EXTENSIONS = ["txt", "json"]
const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'tga', 'webp'] const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "bmp", "tiff", "tif", "tga", "webp"]
function parseBoolean(stringValue) { function parseBoolean(stringValue) {
if (typeof stringValue === 'boolean') { if (typeof stringValue === "boolean") {
return stringValue return stringValue
} }
if (typeof stringValue === 'number') { if (typeof stringValue === "number") {
return stringValue !== 0 return stringValue !== 0
} }
if (typeof stringValue !== 'string') { if (typeof stringValue !== "string") {
return false return false
} }
switch (stringValue?.toLowerCase()?.trim()) { switch (stringValue?.toLowerCase()?.trim()) {
@ -19,7 +19,7 @@ function parseBoolean(stringValue) {
case "yes": case "yes":
case "on": case "on":
case "1": case "1":
return true; return true
case "false": case "false":
case "no": case "no":
@ -28,45 +28,50 @@ function parseBoolean(stringValue) {
case "none": case "none":
case null: case null:
case undefined: case undefined:
return false; return false
} }
try { try {
return Boolean(JSON.parse(stringValue)); return Boolean(JSON.parse(stringValue))
} catch { } catch {
return Boolean(stringValue) return Boolean(stringValue)
} }
} }
const TASK_MAPPING = { const TASK_MAPPING = {
prompt: { name: 'Prompt', prompt: {
name: "Prompt",
setUI: (prompt) => { setUI: (prompt) => {
promptField.value = prompt promptField.value = prompt
}, },
readUI: () => promptField.value, readUI: () => promptField.value,
parse: (val) => val parse: (val) => val
}, },
negative_prompt: { name: 'Negative Prompt', negative_prompt: {
name: "Negative Prompt",
setUI: (negative_prompt) => { setUI: (negative_prompt) => {
negativePromptField.value = negative_prompt negativePromptField.value = negative_prompt
}, },
readUI: () => negativePromptField.value, readUI: () => negativePromptField.value,
parse: (val) => val parse: (val) => val
}, },
active_tags: { name: "Image Modifiers", active_tags: {
name: "Image Modifiers",
setUI: (active_tags) => { setUI: (active_tags) => {
refreshModifiersState(active_tags) refreshModifiersState(active_tags)
}, },
readUI: () => activeTags.map(x => x.name), readUI: () => activeTags.map((x) => x.name),
parse: (val) => val parse: (val) => val
}, },
inactive_tags: { name: "Inactive Image Modifiers", inactive_tags: {
name: "Inactive Image Modifiers",
setUI: (inactive_tags) => { setUI: (inactive_tags) => {
refreshInactiveTags(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 parse: (val) => val
}, },
width: { name: 'Width', width: {
name: "Width",
setUI: (width) => { setUI: (width) => {
const oldVal = widthField.value const oldVal = widthField.value
widthField.value = width widthField.value = width
@ -77,7 +82,8 @@ const TASK_MAPPING = {
readUI: () => parseInt(widthField.value), readUI: () => parseInt(widthField.value),
parse: (val) => parseInt(val) parse: (val) => parseInt(val)
}, },
height: { name: 'Height', height: {
name: "Height",
setUI: (height) => { setUI: (height) => {
const oldVal = heightField.value const oldVal = heightField.value
heightField.value = height heightField.value = height
@ -88,7 +94,8 @@ const TASK_MAPPING = {
readUI: () => parseInt(heightField.value), readUI: () => parseInt(heightField.value),
parse: (val) => parseInt(val) parse: (val) => parseInt(val)
}, },
seed: { name: 'Seed', seed: {
name: "Seed",
setUI: (seed) => { setUI: (seed) => {
if (!seed) { if (!seed) {
randomSeedField.checked = true randomSeedField.checked = true
@ -97,21 +104,23 @@ const TASK_MAPPING = {
return return
} }
randomSeedField.checked = false 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.disabled = false
seedField.value = seed seedField.value = seed
}, },
readUI: () => parseInt(seedField.value), // just return the value the user is seeing in the UI readUI: () => parseInt(seedField.value), // just return the value the user is seeing in the UI
parse: (val) => parseInt(val) parse: (val) => parseInt(val)
}, },
num_inference_steps: { name: 'Steps', num_inference_steps: {
name: "Steps",
setUI: (num_inference_steps) => { setUI: (num_inference_steps) => {
numInferenceStepsField.value = num_inference_steps numInferenceStepsField.value = num_inference_steps
}, },
readUI: () => parseInt(numInferenceStepsField.value), readUI: () => parseInt(numInferenceStepsField.value),
parse: (val) => parseInt(val) parse: (val) => parseInt(val)
}, },
guidance_scale: { name: 'Guidance Scale', guidance_scale: {
name: "Guidance Scale",
setUI: (guidance_scale) => { setUI: (guidance_scale) => {
guidanceScaleField.value = guidance_scale guidanceScaleField.value = guidance_scale
updateGuidanceScaleSlider() updateGuidanceScaleSlider()
@ -119,7 +128,8 @@ const TASK_MAPPING = {
readUI: () => parseFloat(guidanceScaleField.value), readUI: () => parseFloat(guidanceScaleField.value),
parse: (val) => parseFloat(val) parse: (val) => parseFloat(val)
}, },
prompt_strength: { name: 'Prompt Strength', prompt_strength: {
name: "Prompt Strength",
setUI: (prompt_strength) => { setUI: (prompt_strength) => {
promptStrengthField.value = prompt_strength promptStrengthField.value = prompt_strength
updatePromptStrengthSlider() updatePromptStrengthSlider()
@ -128,16 +138,19 @@ const TASK_MAPPING = {
parse: (val) => parseFloat(val) parse: (val) => parseFloat(val)
}, },
init_image: { name: 'Initial Image', init_image: {
name: "Initial Image",
setUI: (init_image) => { setUI: (init_image) => {
initImagePreview.src = init_image initImagePreview.src = init_image
}, },
readUI: () => initImagePreview.src, readUI: () => initImagePreview.src,
parse: (val) => val parse: (val) => val
}, },
mask: { name: 'Mask', mask: {
name: "Mask",
setUI: (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) imageInpainter.setImg(mask)
}, 250) }, 250)
maskSetting.checked = Boolean(mask) maskSetting.checked = Boolean(mask)
@ -145,7 +158,8 @@ const TASK_MAPPING = {
readUI: () => (maskSetting.checked ? imageInpainter.getImg() : undefined), readUI: () => (maskSetting.checked ? imageInpainter.getImg() : undefined),
parse: (val) => val 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) => { setUI: (preserve_init_image_color_profile) => {
applyColorCorrectionField.checked = parseBoolean(preserve_init_image_color_profile) applyColorCorrectionField.checked = parseBoolean(preserve_init_image_color_profile)
}, },
@ -153,14 +167,17 @@ const TASK_MAPPING = {
parse: (val) => parseBoolean(val) parse: (val) => parseBoolean(val)
}, },
use_face_correction: { name: 'Use Face Correction', use_face_correction: {
name: "Use Face Correction",
setUI: (use_face_correction) => { setUI: (use_face_correction) => {
const oldVal = gfpganModelField.value const oldVal = gfpganModelField.value
gfpganModelField.value = getModelPath(use_face_correction, ['.pth']) gfpganModelField.value = getModelPath(use_face_correction, [".pth"])
if (gfpganModelField.value) { // Is a valid value for the field. if (gfpganModelField.value) {
// Is a valid value for the field.
useFaceCorrectionField.checked = true useFaceCorrectionField.checked = true
gfpganModelField.disabled = false 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.disabled = true
gfpganModelField.value = oldVal gfpganModelField.value = oldVal
useFaceCorrectionField.checked = false useFaceCorrectionField.checked = false
@ -171,15 +188,18 @@ const TASK_MAPPING = {
readUI: () => (useFaceCorrectionField.checked ? gfpganModelField.value : undefined), readUI: () => (useFaceCorrectionField.checked ? gfpganModelField.value : undefined),
parse: (val) => val parse: (val) => val
}, },
use_upscale: { name: 'Use Upscaling', use_upscale: {
name: "Use Upscaling",
setUI: (use_upscale) => { setUI: (use_upscale) => {
const oldVal = upscaleModelField.value const oldVal = upscaleModelField.value
upscaleModelField.value = getModelPath(use_upscale, ['.pth']) upscaleModelField.value = getModelPath(use_upscale, [".pth"])
if (upscaleModelField.value) { // Is a valid value for the field. if (upscaleModelField.value) {
// Is a valid value for the field.
useUpscalingField.checked = true useUpscalingField.checked = true
upscaleModelField.disabled = false upscaleModelField.disabled = false
upscaleAmountField.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 upscaleModelField.disabled = true
upscaleAmountField.disabled = true upscaleAmountField.disabled = true
upscaleModelField.value = oldVal upscaleModelField.value = oldVal
@ -189,25 +209,28 @@ const TASK_MAPPING = {
readUI: () => (useUpscalingField.checked ? upscaleModelField.value : undefined), readUI: () => (useUpscalingField.checked ? upscaleModelField.value : undefined),
parse: (val) => val parse: (val) => val
}, },
upscale_amount: { name: 'Upscale By', upscale_amount: {
name: "Upscale By",
setUI: (upscale_amount) => { setUI: (upscale_amount) => {
upscaleAmountField.value = upscale_amount upscaleAmountField.value = upscale_amount
}, },
readUI: () => upscaleAmountField.value, readUI: () => upscaleAmountField.value,
parse: (val) => val parse: (val) => val
}, },
sampler_name: { name: 'Sampler', sampler_name: {
name: "Sampler",
setUI: (sampler_name) => { setUI: (sampler_name) => {
samplerField.value = sampler_name samplerField.value = sampler_name
}, },
readUI: () => samplerField.value, readUI: () => samplerField.value,
parse: (val) => val parse: (val) => val
}, },
use_stable_diffusion_model: { name: 'Stable Diffusion model', use_stable_diffusion_model: {
name: "Stable Diffusion model",
setUI: (use_stable_diffusion_model) => { setUI: (use_stable_diffusion_model) => {
const oldVal = stableDiffusionModelField.value 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 stableDiffusionModelField.value = use_stable_diffusion_model
if (!stableDiffusionModelField.value) { if (!stableDiffusionModelField.value) {
@ -217,35 +240,42 @@ const TASK_MAPPING = {
readUI: () => stableDiffusionModelField.value, readUI: () => stableDiffusionModelField.value,
parse: (val) => val parse: (val) => val
}, },
use_vae_model: { name: 'VAE model', use_vae_model: {
name: "VAE model",
setUI: (use_vae_model) => { setUI: (use_vae_model) => {
const oldVal = vaeModelField.value 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 !== '') { if (use_vae_model !== "") {
use_vae_model = getModelPath(use_vae_model, ['.vae.pt', '.ckpt']) use_vae_model = getModelPath(use_vae_model, [".vae.pt", ".ckpt"])
use_vae_model = use_vae_model !== '' ? use_vae_model : oldVal use_vae_model = use_vae_model !== "" ? use_vae_model : oldVal
} }
vaeModelField.value = use_vae_model vaeModelField.value = use_vae_model
}, },
readUI: () => vaeModelField.value, readUI: () => vaeModelField.value,
parse: (val) => val parse: (val) => val
}, },
use_lora_model: { name: 'LoRA model', use_lora_model: {
name: "LoRA model",
setUI: (use_lora_model) => { setUI: (use_lora_model) => {
const oldVal = loraModelField.value 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 !== '') { if (use_lora_model !== "") {
use_lora_model = getModelPath(use_lora_model, ['.ckpt', '.safetensors']) use_lora_model = getModelPath(use_lora_model, [".ckpt", ".safetensors"])
use_lora_model = use_lora_model !== '' ? use_lora_model : oldVal use_lora_model = use_lora_model !== "" ? use_lora_model : oldVal
} }
loraModelField.value = use_lora_model loraModelField.value = use_lora_model
}, },
readUI: () => loraModelField.value, readUI: () => loraModelField.value,
parse: (val) => val parse: (val) => val
}, },
lora_alpha: { name: 'LoRA Strength', lora_alpha: {
name: "LoRA Strength",
setUI: (lora_alpha) => { setUI: (lora_alpha) => {
loraAlphaField.value = lora_alpha loraAlphaField.value = lora_alpha
updateLoraAlphaSlider() updateLoraAlphaSlider()
@ -253,22 +283,29 @@ const TASK_MAPPING = {
readUI: () => parseFloat(loraAlphaField.value), readUI: () => parseFloat(loraAlphaField.value),
parse: (val) => parseFloat(val) parse: (val) => parseFloat(val)
}, },
use_hypernetwork_model: { name: 'Hypernetwork model', use_hypernetwork_model: {
name: "Hypernetwork model",
setUI: (use_hypernetwork_model) => { setUI: (use_hypernetwork_model) => {
const oldVal = hypernetworkModelField.value 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 !== '') { if (use_hypernetwork_model !== "") {
use_hypernetwork_model = getModelPath(use_hypernetwork_model, ['.pt']) use_hypernetwork_model = getModelPath(use_hypernetwork_model, [".pt"])
use_hypernetwork_model = use_hypernetwork_model !== '' ? use_hypernetwork_model : oldVal use_hypernetwork_model = use_hypernetwork_model !== "" ? use_hypernetwork_model : oldVal
} }
hypernetworkModelField.value = use_hypernetwork_model hypernetworkModelField.value = use_hypernetwork_model
hypernetworkModelField.dispatchEvent(new Event('change')) hypernetworkModelField.dispatchEvent(new Event("change"))
}, },
readUI: () => hypernetworkModelField.value, readUI: () => hypernetworkModelField.value,
parse: (val) => val parse: (val) => val
}, },
hypernetwork_strength: { name: 'Hypernetwork Strength', hypernetwork_strength: {
name: "Hypernetwork Strength",
setUI: (hypernetwork_strength) => { setUI: (hypernetwork_strength) => {
hypernetworkStrengthField.value = hypernetwork_strength hypernetworkStrengthField.value = hypernetwork_strength
updateHypernetworkStrengthSlider() updateHypernetworkStrengthSlider()
@ -277,7 +314,8 @@ const TASK_MAPPING = {
parse: (val) => parseFloat(val) parse: (val) => parseFloat(val)
}, },
num_outputs: { name: 'Parallel Images', num_outputs: {
name: "Parallel Images",
setUI: (num_outputs) => { setUI: (num_outputs) => {
numOutputsParallelField.value = num_outputs numOutputsParallelField.value = num_outputs
}, },
@ -285,7 +323,8 @@ const TASK_MAPPING = {
parse: (val) => val parse: (val) => val
}, },
use_cpu: { name: 'Use CPU', use_cpu: {
name: "Use CPU",
setUI: (use_cpu) => { setUI: (use_cpu) => {
useCPUField.checked = use_cpu useCPUField.checked = use_cpu
}, },
@ -293,28 +332,32 @@ const TASK_MAPPING = {
parse: (val) => val parse: (val) => val
}, },
stream_image_progress: { name: 'Stream Image Progress', stream_image_progress: {
name: "Stream Image Progress",
setUI: (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, readUI: () => streamImageProgressField.checked,
parse: (val) => Boolean(val) 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) => { setUI: (show_only_filtered_image) => {
showOnlyFilteredImageField.checked = show_only_filtered_image showOnlyFilteredImageField.checked = show_only_filtered_image
}, },
readUI: () => showOnlyFilteredImageField.checked, readUI: () => showOnlyFilteredImageField.checked,
parse: (val) => Boolean(val) parse: (val) => Boolean(val)
}, },
output_format: { name: 'Output Format', output_format: {
name: "Output Format",
setUI: (output_format) => { setUI: (output_format) => {
outputFormatField.value = output_format outputFormatField.value = output_format
}, },
readUI: () => outputFormatField.value, readUI: () => outputFormatField.value,
parse: (val) => val 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) => { setUI: (save_to_disk_path) => {
saveToDiskField.checked = Boolean(save_to_disk_path) saveToDiskField.checked = Boolean(save_to_disk_path)
diskPathField.value = save_to_disk_path diskPathField.value = save_to_disk_path
@ -327,14 +370,14 @@ const TASK_MAPPING = {
function restoreTaskToUI(task, fieldsToSkip) { function restoreTaskToUI(task, fieldsToSkip) {
fieldsToSkip = fieldsToSkip || [] fieldsToSkip = fieldsToSkip || []
if ('numOutputsTotal' in task) { if ("numOutputsTotal" in task) {
numOutputsTotalField.value = task.numOutputsTotal numOutputsTotalField.value = task.numOutputsTotal
} }
if ('seed' in task) { if ("seed" in task) {
randomSeedField.checked = false randomSeedField.checked = false
seedField.value = task.seed seedField.value = task.seed
} }
if (!('reqBody' in task)) { if (!("reqBody" in task)) {
return return
} }
for (const key in TASK_MAPPING) { for (const key in TASK_MAPPING) {
@ -344,31 +387,31 @@ function restoreTaskToUI(task, fieldsToSkip) {
} }
// properly reset fields not present in the task // 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.value = ""
hypernetworkModelField.dispatchEvent(new Event("change")) hypernetworkModelField.dispatchEvent(new Event("change"))
} }
if (!('use_lora_model' in task.reqBody)) { if (!("use_lora_model" in task.reqBody)) {
loraModelField.value = "" loraModelField.value = ""
loraModelField.dispatchEvent(new Event("change")) 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) // 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 promptField.value = task.reqBody.original_prompt
if (!('original_prompt' in task.reqBody)) { if (!("original_prompt" in task.reqBody)) {
promptField.value = task.reqBody.prompt promptField.value = task.reqBody.prompt
} }
// properly reset checkboxes // properly reset checkboxes
if (!('use_face_correction' in task.reqBody)) { if (!("use_face_correction" in task.reqBody)) {
useFaceCorrectionField.checked = false useFaceCorrectionField.checked = false
gfpganModelField.disabled = true gfpganModelField.disabled = true
} }
if (!('use_upscale' in task.reqBody)) { if (!("use_upscale" in task.reqBody)) {
useUpscalingField.checked = false useUpscalingField.checked = false
} }
if (!('mask' in task.reqBody) && maskSetting.checked) { if (!("mask" in task.reqBody) && maskSetting.checked) {
maskSetting.checked = false maskSetting.checked = false
maskSetting.dispatchEvent(new Event("click")) maskSetting.dispatchEvent(new Event("click"))
} }
@ -379,15 +422,18 @@ function restoreTaskToUI(task, fieldsToSkip) {
if (IMAGE_REGEX.test(initImagePreview.src) && task.reqBody.init_image == undefined) { if (IMAGE_REGEX.test(initImagePreview.src) && task.reqBody.init_image == undefined) {
// hide source image // hide source image
initImageClearBtn.dispatchEvent(new Event("click")) 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) // listen for inpainter loading event, which happens AFTER the main image loads (which reloads the inpainter)
initImagePreview.addEventListener('load', function() { initImagePreview.addEventListener(
"load",
function() {
if (Boolean(task.reqBody.mask)) { if (Boolean(task.reqBody.mask)) {
imageInpainter.setImg(task.reqBody.mask) imageInpainter.setImg(task.reqBody.mask)
maskSetting.checked = true maskSetting.checked = true
} }
}, { once: true }) },
{ once: true }
)
initImagePreview.src = task.reqBody.init_image initImagePreview.src = task.reqBody.init_image
} }
} }
@ -397,28 +443,26 @@ function readUI() {
reqBody[key] = TASK_MAPPING[key].readUI() reqBody[key] = TASK_MAPPING[key].readUI()
} }
return { return {
'numOutputsTotal': parseInt(numOutputsTotalField.value), numOutputsTotal: parseInt(numOutputsTotalField.value),
'seed': TASK_MAPPING['seed'].readUI(), seed: TASK_MAPPING["seed"].readUI(),
'reqBody': reqBody reqBody: reqBody
} }
} }
function getModelPath(filename, extensions) function getModelPath(filename, extensions) {
{
if (typeof filename !== "string") { if (typeof filename !== "string") {
return return
} }
let pathIdx let pathIdx
if (filename.includes('/models/stable-diffusion/')) { if (filename.includes("/models/stable-diffusion/")) {
pathIdx = filename.indexOf('/models/stable-diffusion/') + 25 // Linux, Mac paths pathIdx = filename.indexOf("/models/stable-diffusion/") + 25 // Linux, Mac paths
} } else if (filename.includes("\\models\\stable-diffusion\\")) {
else if (filename.includes('\\models\\stable-diffusion\\')) { pathIdx = filename.indexOf("\\models\\stable-diffusion\\") + 25 // Linux, Mac paths
pathIdx = filename.indexOf('\\models\\stable-diffusion\\') + 25 // Linux, Mac paths
} }
if (pathIdx >= 0) { if (pathIdx >= 0) {
filename = filename.slice(pathIdx) filename = filename.slice(pathIdx)
} }
extensions.forEach(ext => { extensions.forEach((ext) => {
if (filename.endsWith(ext)) { if (filename.endsWith(ext)) {
filename = filename.slice(0, filename.length - ext.length) filename = filename.slice(0, filename.length - ext.length)
} }
@ -427,26 +471,26 @@ function getModelPath(filename, extensions)
} }
const TASK_TEXT_MAPPING = { const TASK_TEXT_MAPPING = {
prompt: 'Prompt', prompt: "Prompt",
width: 'Width', width: "Width",
height: 'Height', height: "Height",
seed: 'Seed', seed: "Seed",
num_inference_steps: 'Steps', num_inference_steps: "Steps",
guidance_scale: 'Guidance Scale', guidance_scale: "Guidance Scale",
prompt_strength: 'Prompt Strength', prompt_strength: "Prompt Strength",
use_face_correction: 'Use Face Correction', use_face_correction: "Use Face Correction",
use_upscale: 'Use Upscaling', use_upscale: "Use Upscaling",
upscale_amount: 'Upscale By', upscale_amount: "Upscale By",
sampler_name: 'Sampler', sampler_name: "Sampler",
negative_prompt: 'Negative Prompt', negative_prompt: "Negative Prompt",
use_stable_diffusion_model: 'Stable Diffusion model', use_stable_diffusion_model: "Stable Diffusion model",
use_hypernetwork_model: 'Hypernetwork model', use_hypernetwork_model: "Hypernetwork model",
hypernetwork_strength: 'Hypernetwork Strength' hypernetwork_strength: "Hypernetwork Strength"
} }
function parseTaskFromText(str) { function parseTaskFromText(str) {
const taskReqBody = {} const taskReqBody = {}
const lines = str.split('\n') const lines = str.split("\n")
if (lines.length === 0) { if (lines.length === 0) {
return return
} }
@ -454,14 +498,14 @@ function parseTaskFromText(str) {
// Prompt // Prompt
let knownKeyOnFirstLine = false let knownKeyOnFirstLine = false
for (let key in TASK_TEXT_MAPPING) { 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 knownKeyOnFirstLine = true
break break
} }
} }
if (!knownKeyOnFirstLine) { if (!knownKeyOnFirstLine) {
taskReqBody.prompt = lines[0] taskReqBody.prompt = lines[0]
console.log('Prompt:', taskReqBody.prompt) console.log("Prompt:", taskReqBody.prompt)
} }
for (const key in TASK_TEXT_MAPPING) { for (const key in TASK_TEXT_MAPPING) {
@ -469,18 +513,18 @@ function parseTaskFromText(str) {
continue continue
} }
const name = TASK_TEXT_MAPPING[key]; const name = TASK_TEXT_MAPPING[key]
let val = undefined let val = undefined
const reName = new RegExp(`${name}\\ *:\\ *(.*)(?:\\r\\n|\\r|\\n)*`, 'igm') const reName = new RegExp(`${name}\\ *:\\ *(.*)(?:\\r\\n|\\r|\\n)*`, "igm")
const match = reName.exec(str); const match = reName.exec(str)
if (match) { if (match) {
str = str.slice(0, match.index) + str.slice(match.index + match[0].length) str = str.slice(0, match.index) + str.slice(match.index + match[0].length)
val = match[1] val = match[1]
} }
if (val !== undefined) { if (val !== undefined) {
taskReqBody[key] = TASK_MAPPING[key].parse(val.trim()) 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) { if (!str) {
break break
} }
@ -490,18 +534,19 @@ function parseTaskFromText(str) {
return undefined return undefined
} }
const task = { reqBody: taskReqBody } const task = { reqBody: taskReqBody }
if ('seed' in taskReqBody) { if ("seed" in taskReqBody) {
task.seed = taskReqBody.seed task.seed = taskReqBody.seed
} }
return task return task
} }
async function parseContent(text) { async function parseContent(text) {
text = text.trim(); text = text.trim()
if (text.startsWith('{') && text.endsWith('}')) { if (text.startsWith("{") && text.endsWith("}")) {
try { try {
const task = JSON.parse(text) 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) task.reqBody = Object.assign({}, task)
} }
restoreTaskToUI(task) restoreTaskToUI(task)
@ -513,7 +558,8 @@ async function parseContent(text) {
} }
// Normal txt file. // Normal txt file.
const task = parseTaskFromText(text) 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) restoreTaskToUI(task)
return true return true
} else { } else {
@ -530,21 +576,25 @@ async function readFile(file, i) {
} }
function dropHandler(ev) { function dropHandler(ev) {
console.log('Content dropped...') console.log("Content dropped...")
let items = [] let items = []
if (ev?.dataTransfer?.items) { // Use DataTransferItemList interface if (ev?.dataTransfer?.items) {
// Use DataTransferItemList interface
items = Array.from(ev.dataTransfer.items) items = Array.from(ev.dataTransfer.items)
items = items.filter(item => item.kind === 'file') items = items.filter((item) => item.kind === "file")
items = items.map(item => item.getAsFile()) items = items.map((item) => item.getAsFile())
} else if (ev?.dataTransfer?.files) { // Use DataTransfer interface } else if (ev?.dataTransfer?.files) {
// Use DataTransfer interface
items = Array.from(ev.dataTransfer.files) 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 text_items = items.filter((item) => TEXT_EXTENSIONS.includes(item.file_ext))
let image_items = items.filter(item => IMAGE_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) { if (image_items.length > 0 && ev.target == initImageSelector) {
return // let the event bubble up, so that the Init Image filepicker can receive this 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) text_items.forEach(readFile)
} }
function dragOverHandler(ev) { function dragOverHandler(ev) {
console.log('Content in drop zone') console.log("Content in drop zone")
// Prevent default behavior (Prevent file/content from being opened) // Prevent default behavior (Prevent file/content from being opened)
ev.preventDefault() ev.preventDefault()
@ -562,53 +612,52 @@ function dragOverHandler(ev) {
ev.dataTransfer.dropEffect = "copy" ev.dataTransfer.dropEffect = "copy"
let img = new Image() 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) ev.dataTransfer.setDragImage(img, 16, 16)
} }
document.addEventListener("drop", dropHandler) document.addEventListener("drop", dropHandler)
document.addEventListener("dragover", dragOverHandler) document.addEventListener("dragover", dragOverHandler)
const TASK_REQ_NO_EXPORT = [ const TASK_REQ_NO_EXPORT = ["use_cpu", "save_to_disk_path"]
"use_cpu", const resetSettings = document.getElementById("reset-image-settings")
"save_to_disk_path"
]
const resetSettings = document.getElementById('reset-image-settings')
function checkReadTextClipboardPermission(result) { function checkReadTextClipboardPermission(result) {
if (result.state != "granted" && result.state != "prompt") { if (result.state != "granted" && result.state != "prompt") {
return return
} }
// PASTE ICON // PASTE ICON
const pasteIcon = document.createElement('i') const pasteIcon = document.createElement("i")
pasteIcon.className = 'fa-solid fa-paste section-button' pasteIcon.className = "fa-solid fa-paste section-button"
pasteIcon.innerHTML = `<span class="simple-tooltip top-left">Paste Image Settings</span>` pasteIcon.innerHTML = `<span class="simple-tooltip top-left">Paste Image Settings</span>`
pasteIcon.addEventListener('click', async (event) => { pasteIcon.addEventListener("click", async (event) => {
event.stopPropagation() event.stopPropagation()
// Add css class 'active' // Add css class 'active'
pasteIcon.classList.add('active') pasteIcon.classList.add("active")
// In 350 ms remove the 'active' class // 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 // Retrieve clipboard content and try to parse it
const text = await navigator.clipboard.readText(); const text = await navigator.clipboard.readText()
await parseContent(text) await parseContent(text)
}) })
resetSettings.parentNode.insertBefore(pasteIcon, resetSettings) 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) { if (event.target) {
const targetTag = event.target.tagName.toLowerCase() const targetTag = event.target.tagName.toLowerCase()
// Disable when targeting input elements. // Disable when targeting input elements.
if (targetTag === 'input' || targetTag === 'textarea') { if (targetTag === "input" || targetTag === "textarea") {
return return
} }
} }
const paste = (event.clipboardData || window.clipboardData).getData('text') const paste = (event.clipboardData || window.clipboardData).getData("text")
const selection = window.getSelection() 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() event.preventDefault()
return return
} }
@ -620,15 +669,15 @@ function checkWriteToClipboardPermission (result) {
return return
} }
// COPY ICON // COPY ICON
const copyIcon = document.createElement('i') const copyIcon = document.createElement("i")
copyIcon.className = 'fa-solid fa-clipboard section-button' copyIcon.className = "fa-solid fa-clipboard section-button"
copyIcon.innerHTML = `<span class="simple-tooltip top-left">Copy Image Settings</span>` copyIcon.innerHTML = `<span class="simple-tooltip top-left">Copy Image Settings</span>`
copyIcon.addEventListener('click', (event) => { copyIcon.addEventListener("click", (event) => {
event.stopPropagation() event.stopPropagation()
// Add css class 'active' // Add css class 'active'
copyIcon.classList.add('active') copyIcon.classList.add("active")
// In 350 ms remove the 'active' class // In 350 ms remove the 'active' class
asyncDelay(350).then(() => copyIcon.classList.remove('active')) asyncDelay(350).then(() => copyIcon.classList.remove("active"))
const uiState = readUI() const uiState = readUI()
TASK_REQ_NO_EXPORT.forEach((key) => delete uiState.reqBody[key]) TASK_REQ_NO_EXPORT.forEach((key) => delete uiState.reqBody[key])
if (uiState.reqBody.init_image && !IMAGE_REGEX.test(uiState.reqBody.init_image)) { if (uiState.reqBody.init_image && !IMAGE_REGEX.test(uiState.reqBody.init_image)) {
@ -641,7 +690,7 @@ function checkWriteToClipboardPermission (result) {
} }
// Determine which access we have to the clipboard. Clipboard access is only available on localhost or via TLS. // 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) => { 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 // Fix for firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1560373
checkWriteToClipboardPermission({ state: "granted" }) checkWriteToClipboardPermission({ state: "granted" })
} }

File diff suppressed because it is too large Load Diff

View File

@ -6,14 +6,14 @@ const IMAGE_EDITOR_BUTTONS = [
{ {
name: "Cancel", name: "Cancel",
icon: "fa-regular fa-circle-xmark", icon: "fa-regular fa-circle-xmark",
handler: editor => { handler: (editor) => {
editor.hide() editor.hide()
} }
}, },
{ {
name: "Save", name: "Save",
icon: "fa-solid fa-floppy-disk", icon: "fa-solid fa-floppy-disk",
handler: editor => { handler: (editor) => {
editor.saveImage() editor.saveImage()
} }
} }
@ -104,9 +104,9 @@ const IMAGE_EDITOR_TOOLS = [
var drawn_rgb = editor.ctx_current.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 var drawn_opacity = drawn_rgb[3] / 255
editor.custom_color_input.value = rgbToHex({ editor.custom_color_input.value = rgbToHex({
r: (drawn_rgb[0] * drawn_opacity) + (img_rgb[0] * (1 - drawn_opacity)), r: drawn_rgb[0] * drawn_opacity + img_rgb[0] * (1 - drawn_opacity),
g: (drawn_rgb[1] * drawn_opacity) + (img_rgb[1] * (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)), b: drawn_rgb[2] * drawn_opacity + img_rgb[2] * (1 - drawn_opacity)
}) })
editor.custom_color_input.dispatchEvent(new Event("change")) editor.custom_color_input.dispatchEvent(new Event("change"))
} }
@ -123,7 +123,7 @@ const IMAGE_EDITOR_ACTIONS = [
className: "load_mask", className: "load_mask",
icon: "fa-regular fa-folder-open", icon: "fa-regular fa-folder-open",
handler: (editor) => { handler: (editor) => {
let el = document.createElement('input') let el = document.createElement("input")
el.setAttribute("type", "file") el.setAttribute("type", "file")
el.addEventListener("change", function() { el.addEventListener("change", function() {
if (this.files.length === 0) { if (this.files.length === 0) {
@ -133,7 +133,7 @@ const IMAGE_EDITOR_ACTIONS = [
let reader = new FileReader() let reader = new FileReader()
let file = this.files[0] let file = this.files[0]
reader.addEventListener('load', function(event) { reader.addEventListener("load", function(event) {
let maskData = reader.result let maskData = reader.result
editor.layers.drawing.ctx.clearRect(0, 0, editor.width, editor.height) editor.layers.drawing.ctx.clearRect(0, 0, editor.width, editor.height)
@ -200,13 +200,13 @@ var IMAGE_EDITOR_SECTIONS = [
name: "tool", name: "tool",
title: "Tool", title: "Tool",
default: "draw", default: "draw",
options: Array.from(IMAGE_EDITOR_TOOLS.map(t => t.id)), options: Array.from(IMAGE_EDITOR_TOOLS.map((t) => t.id)),
initElement: (element, option) => { initElement: (element, option) => {
var tool_info = IMAGE_EDITOR_TOOLS.find(t => t.id == option) var tool_info = IMAGE_EDITOR_TOOLS.find((t) => t.id == option)
element.className = "image-editor-button button" element.className = "image-editor-button button"
var sub_element = document.createElement("div") var sub_element = document.createElement("div")
var icon = document.createElement("i") var icon = document.createElement("i")
tool_info.icon.split(" ").forEach(c => icon.classList.add(c)) tool_info.icon.split(" ").forEach((c) => icon.classList.add(c))
sub_element.appendChild(icon) sub_element.appendChild(icon)
sub_element.append(tool_info.name) sub_element.append(tool_info.name)
element.appendChild(sub_element) element.appendChild(sub_element)
@ -218,14 +218,46 @@ var IMAGE_EDITOR_SECTIONS = [
default: "#f1c232", default: "#f1c232",
options: [ options: [
"custom", "custom",
"#ea9999", "#e06666", "#cc0000", "#990000", "#660000", "#ea9999",
"#f9cb9c", "#f6b26b", "#e69138", "#b45f06", "#783f04", "#e06666",
"#ffe599", "#ffd966", "#f1c232", "#bf9000", "#7f6000", "#cc0000",
"#b6d7a8", "#93c47d", "#6aa84f", "#38761d", "#274e13", "#990000",
"#a4c2f4", "#6d9eeb", "#3c78d8", "#1155cc", "#1c4587", "#660000",
"#b4a7d6", "#8e7cc3", "#674ea7", "#351c75", "#20124d", "#f9cb9c",
"#d5a6bd", "#c27ba0", "#a64d79", "#741b47", "#4c1130", "#f6b26b",
"#ffffff", "#c0c0c0", "#838383", "#525252", "#000000", "#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) => { initElement: (element, option) => {
if (option == "custom") { if (option == "custom") {
@ -238,12 +270,11 @@ var IMAGE_EDITOR_SECTIONS = [
input.click() input.click()
} }
element.appendChild(span) element.appendChild(span)
} } else {
else {
element.style.background = option element.style.background = option
} }
}, },
getCustom: editor => { getCustom: (editor) => {
var input = editor.popup.querySelector(".image_editor_color input") var input = editor.popup.querySelector(".image_editor_color input")
return input.value return input.value
} }
@ -257,7 +288,7 @@ var IMAGE_EDITOR_SECTIONS = [
element.parentElement.style.flex = option element.parentElement.style.flex = option
element.style.width = option + "px" element.style.width = option + "px"
element.style.height = option + "px" element.style.height = option + "px"
element.style['margin-right'] = '2px' element.style["margin-right"] = "2px"
element.style["border-radius"] = (option / 2).toFixed() + "px" element.style["border-radius"] = (option / 2).toFixed() + "px"
} }
}, },
@ -283,7 +314,7 @@ var IMAGE_EDITOR_SECTIONS = [
sub_element.style.filter = `blur(${blur_amount}px)` sub_element.style.filter = `blur(${blur_amount}px)`
sub_element.style.width = `${size - 2}px` sub_element.style.width = `${size - 2}px`
sub_element.style.height = `${size - 2}px` sub_element.style.height = `${size - 2}px`
sub_element.style['border-radius'] = `${size}px` sub_element.style["border-radius"] = `${size}px`
element.style.background = "none" element.style.background = "none"
element.appendChild(sub_element) element.appendChild(sub_element)
} }
@ -313,7 +344,7 @@ class EditorHistory {
this.push({ this.push({
type: "action", type: "action",
id: action id: action
}); })
} }
editBegin(x, y) { editBegin(x, y) {
this.current_edit = { this.current_edit = {
@ -345,7 +376,7 @@ class EditorHistory {
} }
rewindTo(new_rewind_index) { rewindTo(new_rewind_index) {
if (new_rewind_index < 0 || new_rewind_index > this.events.length) { if (new_rewind_index < 0 || new_rewind_index > this.events.length) {
return; // do nothing if target index is out of bounds return // do nothing if target index is out of bounds
} }
var ctx = this.editor.layers.drawing.ctx var ctx = this.editor.layers.drawing.ctx
@ -361,17 +392,16 @@ class EditorHistory {
} }
if (snapshot_index != -1) { if (snapshot_index != -1) {
ctx.putImageData(this.events[snapshot_index].snapshot, 0, 0); ctx.putImageData(this.events[snapshot_index].snapshot, 0, 0)
} }
for (var i = (snapshot_index + 1); i <= target_index; i++) { for (var i = snapshot_index + 1; i <= target_index; i++) {
var event = this.events[i] var event = this.events[i]
if (event.type == "action") { if (event.type == "action") {
var action = IMAGE_EDITOR_ACTIONS.find(a => a.id == event.id) var action = IMAGE_EDITOR_ACTIONS.find((a) => a.id == event.id)
action.handler(this.editor) action.handler(this.editor)
} } else if (event.type == "edit") {
else if (event.type == "edit") { var tool = IMAGE_EDITOR_TOOLS.find((t) => t.id == event.id)
var tool = IMAGE_EDITOR_TOOLS.find(t => t.id == event.id)
this.editor.setBrush(this.editor.layers.drawing, event.options) this.editor.setBrush(this.editor.layers.drawing, event.options)
var first_point = event.points[0] var first_point = event.points[0]
@ -403,12 +433,8 @@ class ImageEditor {
this.temp_previous_tool = null // used for the ctrl-colorpicker functionality this.temp_previous_tool = null // used for the ctrl-colorpicker functionality
this.container = popup.querySelector(".editor-controls-center > div") this.container = popup.querySelector(".editor-controls-center > div")
this.layers = {} this.layers = {}
var layer_names = [ var layer_names = ["background", "drawing", "overlay"]
"background", layer_names.forEach((name) => {
"drawing",
"overlay"
]
layer_names.forEach(name => {
let canvas = document.createElement("canvas") let canvas = document.createElement("canvas")
canvas.className = `editor-canvas-${name}` canvas.className = `editor-canvas-${name}`
this.container.appendChild(canvas) this.container.appendChild(canvas)
@ -434,7 +460,7 @@ class ImageEditor {
// initialize editor controls // initialize editor controls
this.options = {} this.options = {}
this.optionElements = {} this.optionElements = {}
IMAGE_EDITOR_SECTIONS.forEach(section => { IMAGE_EDITOR_SECTIONS.forEach((section) => {
section.id = `image_editor_${section.name}` section.id = `image_editor_${section.name}`
var sectionElement = document.createElement("div") var sectionElement = document.createElement("div")
sectionElement.className = section.id sectionElement.className = section.id
@ -452,7 +478,7 @@ class ImageEditor {
var optionElement = document.createElement("div") var optionElement = document.createElement("div")
optionHolder.appendChild(optionElement) optionHolder.appendChild(optionElement)
section.initElement(optionElement, option) section.initElement(optionElement, option)
optionElement.addEventListener("click", target => this.selectOption(section.name, index)) optionElement.addEventListener("click", (target) => this.selectOption(section.name, index))
optionsContainer.appendChild(optionHolder) optionsContainer.appendChild(optionHolder)
this.optionElements[section.name].push(optionElement) this.optionElements[section.name].push(optionElement)
}) })
@ -470,13 +496,13 @@ class ImageEditor {
}) })
if (this.inpainter) { if (this.inpainter) {
this.selectOption("color", IMAGE_EDITOR_SECTIONS.find(s => s.name == "color").options.indexOf("#ffffff")) 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)) this.selectOption("opacity", IMAGE_EDITOR_SECTIONS.find((s) => s.name == "opacity").options.indexOf(0.4))
} }
// initialize the right-side controls // initialize the right-side controls
var buttonContainer = document.createElement("div") var buttonContainer = document.createElement("div")
IMAGE_EDITOR_BUTTONS.forEach(button => { IMAGE_EDITOR_BUTTONS.forEach((button) => {
var element = document.createElement("div") var element = document.createElement("div")
var icon = document.createElement("i") var icon = document.createElement("i")
element.className = "image-editor-button button" element.className = "image-editor-button button"
@ -484,13 +510,13 @@ class ImageEditor {
element.appendChild(icon) element.appendChild(icon)
element.append(button.name) element.append(button.name)
buttonContainer.appendChild(element) buttonContainer.appendChild(element)
element.addEventListener("click", event => button.handler(this)) element.addEventListener("click", (event) => button.handler(this))
}) })
var actionsContainer = document.createElement("div") var actionsContainer = document.createElement("div")
var actionsTitle = document.createElement("h4") var actionsTitle = document.createElement("h4")
actionsTitle.textContent = "Actions" actionsTitle.textContent = "Actions"
actionsContainer.appendChild(actionsTitle); actionsContainer.appendChild(actionsTitle)
IMAGE_EDITOR_ACTIONS.forEach(action => { IMAGE_EDITOR_ACTIONS.forEach((action) => {
var element = document.createElement("div") var element = document.createElement("div")
var icon = document.createElement("i") var icon = document.createElement("i")
element.className = "image-editor-button button" element.className = "image-editor-button button"
@ -501,7 +527,7 @@ class ImageEditor {
element.appendChild(icon) element.appendChild(icon)
element.append(action.name) element.append(action.name)
actionsContainer.appendChild(element) actionsContainer.appendChild(element)
element.addEventListener("click", event => this.runAction(action.id)) element.addEventListener("click", (event) => this.runAction(action.id))
}) })
this.popup.querySelector(".editor-controls-right").appendChild(actionsContainer) this.popup.querySelector(".editor-controls-right").appendChild(actionsContainer)
this.popup.querySelector(".editor-controls-right").appendChild(buttonContainer) this.popup.querySelector(".editor-controls-right").appendChild(buttonContainer)
@ -530,8 +556,7 @@ class ImageEditor {
var multiplier = max_size / width var multiplier = max_size / width
width = (multiplier * width).toFixed() width = (multiplier * width).toFixed()
height = (multiplier * height).toFixed() height = (multiplier * height).toFixed()
} } else {
else {
var max_size = Math.min(parseInt(window.innerHeight * 0.9), height, 768) var max_size = Math.min(parseInt(window.innerHeight * 0.9), height, 768)
var multiplier = max_size / height var multiplier = max_size / height
width = (multiplier * width).toFixed() width = (multiplier * width).toFixed()
@ -543,7 +568,7 @@ class ImageEditor {
this.container.style.width = width + "px" this.container.style.width = width + "px"
this.container.style.height = height + "px" this.container.style.height = height + "px"
Object.values(this.layers).forEach(layer => { Object.values(this.layers).forEach((layer) => {
layer.canvas.width = width layer.canvas.width = width
layer.canvas.height = height layer.canvas.height = height
}) })
@ -556,11 +581,11 @@ class ImageEditor {
} }
get tool() { get tool() {
var tool_id = this.getOptionValue("tool") var tool_id = this.getOptionValue("tool")
return IMAGE_EDITOR_TOOLS.find(t => t.id == tool_id); return IMAGE_EDITOR_TOOLS.find((t) => t.id == tool_id)
} }
loadTool() { loadTool() {
this.drawing = false this.drawing = false
this.container.style.cursor = this.tool.cursor; this.container.style.cursor = this.tool.cursor
} }
setImage(url, width, height) { setImage(url, width, height) {
this.setSize(width, height) this.setSize(width, height)
@ -574,8 +599,7 @@ class ImageEditor {
this.layers.background.ctx.drawImage(image, 0, 0, this.width, this.height) this.layers.background.ctx.drawImage(image, 0, 0, this.width, this.height)
} }
image.src = url image.src = url
} } else {
else {
this.layers.background.ctx.fillStyle = "#ffffff" this.layers.background.ctx.fillStyle = "#ffffff"
this.layers.background.ctx.beginPath() this.layers.background.ctx.beginPath()
this.layers.background.ctx.rect(0, 0, this.width, this.height) this.layers.background.ctx.rect(0, 0, this.width, this.height)
@ -589,23 +613,24 @@ class ImageEditor {
this.layers.background.ctx.drawImage(this.layers.drawing.canvas, 0, 0, this.width, this.height) this.layers.background.ctx.drawImage(this.layers.drawing.canvas, 0, 0, this.width, this.height)
var base64 = this.layers.background.canvas.toDataURL() var base64 = this.layers.background.canvas.toDataURL()
initImagePreview.src = base64 // this will trigger the rest of the app to use it initImagePreview.src = base64 // this will trigger the rest of the app to use it
} } else {
else {
// This is an inpainter, so make sure the toggle is set accordingly // This is an inpainter, so make sure the toggle is set accordingly
var is_blank = !this.layers.drawing.ctx var is_blank = !this.layers.drawing.ctx
.getImageData(0, 0, this.width, this.height).data .getImageData(0, 0, this.width, this.height)
.some(channel => channel !== 0) .data.some((channel) => channel !== 0)
maskSetting.checked = !is_blank maskSetting.checked = !is_blank
} }
this.hide() this.hide()
} }
getImg() { // a drop-in replacement of the drawingboard version getImg() {
// a drop-in replacement of the drawingboard version
return this.layers.drawing.canvas.toDataURL() return this.layers.drawing.canvas.toDataURL()
} }
setImg(dataUrl) { // a drop-in replacement of the drawingboard version setImg(dataUrl) {
// a drop-in replacement of the drawingboard version
var image = new Image() var image = new Image()
image.onload = () => { image.onload = () => {
var ctx = this.layers.drawing.ctx; var ctx = this.layers.drawing.ctx
ctx.clearRect(0, 0, this.width, this.height) ctx.clearRect(0, 0, this.width, this.height)
ctx.globalCompositeOperation = "source-over" ctx.globalCompositeOperation = "source-over"
ctx.globalAlpha = 1 ctx.globalAlpha = 1
@ -616,7 +641,7 @@ class ImageEditor {
image.src = dataUrl image.src = dataUrl
} }
runAction(action_id) { runAction(action_id) {
var action = IMAGE_EDITOR_ACTIONS.find(a => a.id == action_id) var action = IMAGE_EDITOR_ACTIONS.find((a) => a.id == action_id)
if (action.trackHistory) { if (action.trackHistory) {
this.history.pushAction(action_id) this.history.pushAction(action_id)
} }
@ -634,15 +659,16 @@ class ImageEditor {
layer.ctx.strokeStyle = options.color layer.ctx.strokeStyle = options.color
var sharpness = parseInt(options.sharpness * options.brush_size) var sharpness = parseInt(options.sharpness * options.brush_size)
layer.ctx.filter = sharpness == 0 ? `none` : `blur(${sharpness}px)` layer.ctx.filter = sharpness == 0 ? `none` : `blur(${sharpness}px)`
layer.ctx.globalAlpha = (1 - options.opacity) layer.ctx.globalAlpha = 1 - options.opacity
layer.ctx.globalCompositeOperation = "source-over" layer.ctx.globalCompositeOperation = "source-over"
var tool = IMAGE_EDITOR_TOOLS.find(t => t.id == options.tool) var tool = IMAGE_EDITOR_TOOLS.find((t) => t.id == options.tool)
if (tool && tool.setBrush) { if (tool && tool.setBrush) {
tool.setBrush(editor, layer) tool.setBrush(editor, layer)
} }
} } else {
else { Object.values(["drawing", "overlay"])
Object.values([ "drawing", "overlay" ]).map(name => this.layers[name]).forEach(l => { .map((name) => this.layers[name])
.forEach((l) => {
this.setBrush(l) this.setBrush(l)
}) })
} }
@ -650,13 +676,15 @@ class ImageEditor {
get ctx_overlay() { get ctx_overlay() {
return this.layers.overlay.ctx return this.layers.overlay.ctx
} }
get ctx_current() { // the idea is this will help support having custom layers and editing each one get ctx_current() {
// the idea is this will help support having custom layers and editing each one
return this.layers.drawing.ctx return this.layers.drawing.ctx
} }
get canvas_current() { get canvas_current() {
return this.layers.drawing.canvas return this.layers.drawing.canvas
} }
keyHandler(event) { // handles keybinds like ctrl+z, ctrl+y keyHandler(event) {
// handles keybinds like ctrl+z, ctrl+y
if (!this.popup.classList.contains("active")) { if (!this.popup.classList.contains("active")) {
document.removeEventListener("keydown", this.keyHandlerBound) document.removeEventListener("keydown", this.keyHandlerBound)
document.removeEventListener("keyup", this.keyHandlerBound) document.removeEventListener("keyup", this.keyHandlerBound)
@ -668,41 +696,45 @@ class ImageEditor {
if ((event.key == "z" || event.key == "Z") && event.ctrlKey) { if ((event.key == "z" || event.key == "Z") && event.ctrlKey) {
if (!event.shiftKey) { if (!event.shiftKey) {
this.history.undo() this.history.undo()
} } else {
else {
this.history.redo() this.history.redo()
} }
event.stopPropagation(); event.stopPropagation()
event.preventDefault(); event.preventDefault()
} }
if (event.key == "y" && event.ctrlKey) { if (event.key == "y" && event.ctrlKey) {
this.history.redo() this.history.redo()
event.stopPropagation(); event.stopPropagation()
event.preventDefault(); event.preventDefault()
} }
if (event.key === "Escape") { if (event.key === "Escape") {
this.hide() this.hide()
event.stopPropagation(); event.stopPropagation()
event.preventDefault(); event.preventDefault()
} }
} }
// dropper ctrl holding handler stuff // dropper ctrl holding handler stuff
var dropper_active = this.temp_previous_tool != null; var dropper_active = this.temp_previous_tool != null
if (dropper_active && !event.ctrlKey) { if (dropper_active && !event.ctrlKey) {
this.selectOption("tool", IMAGE_EDITOR_TOOLS.findIndex(t => t.id == this.temp_previous_tool)) this.selectOption(
"tool",
IMAGE_EDITOR_TOOLS.findIndex((t) => t.id == this.temp_previous_tool)
)
this.temp_previous_tool = null this.temp_previous_tool = null
} } else if (!dropper_active && event.ctrlKey) {
else if (!dropper_active && event.ctrlKey) {
this.temp_previous_tool = this.getOptionValue("tool") this.temp_previous_tool = this.getOptionValue("tool")
this.selectOption("tool", IMAGE_EDITOR_TOOLS.findIndex(t => t.id == "colorpicker")) this.selectOption(
"tool",
IMAGE_EDITOR_TOOLS.findIndex((t) => t.id == "colorpicker")
)
} }
} }
mouseHandler(event) { mouseHandler(event) {
var bbox = this.layers.overlay.canvas.getBoundingClientRect() var bbox = this.layers.overlay.canvas.getBoundingClientRect()
var x = (event.clientX || 0) - bbox.left var x = (event.clientX || 0) - bbox.left
var y = (event.clientY || 0) - bbox.top var y = (event.clientY || 0) - bbox.top
var type = event.type; var type = event.type
var touchmap = { var touchmap = {
touchstart: "mousedown", touchstart: "mousedown",
touchmove: "mousemove", touchmove: "mousemove",
@ -744,15 +776,15 @@ class ImageEditor {
} }
} }
getOptionValue(section_name) { getOptionValue(section_name) {
var section = IMAGE_EDITOR_SECTIONS.find(s => s.name == 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 return this.options && section_name in this.options ? this.options[section_name] : section.default
} }
selectOption(section_name, option_index) { selectOption(section_name, option_index) {
var section = IMAGE_EDITOR_SECTIONS.find(s => s.name == section_name) var section = IMAGE_EDITOR_SECTIONS.find((s) => s.name == section_name)
var value = section.options[option_index] var value = section.options[option_index]
this.options[section_name] = value == "custom" ? section.getCustom(this) : value this.options[section_name] = value == "custom" ? section.getCustom(this) : value
this.optionElements[section_name].forEach(element => element.classList.remove("active")) this.optionElements[section_name].forEach((element) => element.classList.remove("active"))
this.optionElements[section_name][option_index].classList.add("active") this.optionElements[section_name][option_index].classList.add("active")
// change the editor // change the editor
@ -778,7 +810,6 @@ document.getElementById("init_image_button_inpaint").addEventListener("click", (
img2imgUnload() // no init image when the app starts img2imgUnload() // no init image when the app starts
function rgbToHex(rgb) { function rgbToHex(rgb) {
function componentToHex(c) { function componentToHex(c) {
var hex = parseInt(c).toString(16) var hex = parseInt(c).toString(16)
@ -788,12 +819,14 @@ function rgbToHex(rgb) {
} }
function hexToRgb(hex) { function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result ? { return result
? {
r: parseInt(result[1], 16), r: parseInt(result[1], 16),
g: parseInt(result[2], 16), g: parseInt(result[2], 16),
b: parseInt(result[3], 16) b: parseInt(result[3], 16)
} : null; }
: null
} }
function pixelCompare(int1, int2) { function pixelCompare(int1, int2) {
@ -802,82 +835,93 @@ function pixelCompare(int1, int2) {
// adapted from https://ben.akrin.com/canvas_fill/fill_04.html // adapted from https://ben.akrin.com/canvas_fill/fill_04.html
function flood_fill(editor, the_canvas_context, x, y, color) { function flood_fill(editor, the_canvas_context, x, y, color) {
pixel_stack = [{x:x, y:y}] ; pixel_stack = [{ x: x, y: y }]
pixels = the_canvas_context.getImageData( 0, 0, editor.width, editor.height ) ; pixels = the_canvas_context.getImageData(0, 0, editor.width, editor.height)
var linear_cords = ( y * editor.width + x ) * 4 ; var linear_cords = (y * editor.width + x) * 4
var original_color = {r:pixels.data[linear_cords], var original_color = {
r: pixels.data[linear_cords],
g: pixels.data[linear_cords + 1], g: pixels.data[linear_cords + 1],
b: pixels.data[linear_cords + 2], b: pixels.data[linear_cords + 2],
a:pixels.data[linear_cords+3]} ; a: pixels.data[linear_cords + 3]
}
var opacity = color.a / 255; var opacity = color.a / 255
var new_color = { var new_color = {
r: parseInt((color.r * opacity) + (original_color.r * (1 - opacity))), r: parseInt(color.r * opacity + original_color.r * (1 - opacity)),
g: parseInt((color.g * opacity) + (original_color.g * (1 - opacity))), g: parseInt(color.g * opacity + original_color.g * (1 - opacity)),
b: parseInt((color.b * opacity) + (original_color.b * (1 - opacity))) b: parseInt(color.b * opacity + original_color.b * (1 - opacity))
} }
if ((pixelCompare(new_color.r, original_color.r) && if (
pixelCompare(new_color.r, original_color.r) &&
pixelCompare(new_color.g, original_color.g) && pixelCompare(new_color.g, original_color.g) &&
pixelCompare(new_color.b, original_color.b))) pixelCompare(new_color.b, original_color.b)
{ ) {
return; // This color is already the color we want, so do nothing return // This color is already the color we want, so do nothing
} }
var max_stack_size = editor.width * editor.height; var max_stack_size = editor.width * editor.height
while (pixel_stack.length > 0 && pixel_stack.length < max_stack_size) { while (pixel_stack.length > 0 && pixel_stack.length < max_stack_size) {
new_pixel = pixel_stack.shift() ; new_pixel = pixel_stack.shift()
x = new_pixel.x ; x = new_pixel.x
y = new_pixel.y ; y = new_pixel.y
linear_cords = ( y * editor.width + x ) * 4 ; linear_cords = (y * editor.width + x) * 4
while( y-->=0 && while (
(pixelCompare(pixels.data[linear_cords], original_color.r) && y-- >= 0 &&
pixelCompare(pixels.data[linear_cords], original_color.r) &&
pixelCompare(pixels.data[linear_cords + 1], original_color.g) && pixelCompare(pixels.data[linear_cords + 1], original_color.g) &&
pixelCompare(pixels.data[linear_cords+2], original_color.b))) { pixelCompare(pixels.data[linear_cords + 2], original_color.b)
linear_cords -= editor.width * 4 ; ) {
linear_cords -= editor.width * 4
} }
linear_cords += editor.width * 4 ; linear_cords += editor.width * 4
y++ ; y++
var reached_left = false ; var reached_left = false
var reached_right = false ; var reached_right = false
while( y++<editor.height && while (
(pixelCompare(pixels.data[linear_cords], original_color.r) && 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 + 1], original_color.g) &&
pixelCompare(pixels.data[linear_cords+2], original_color.b))) { 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] = new_color.r
pixels.data[linear_cords+2] = new_color.b ; pixels.data[linear_cords + 1] = new_color.g
pixels.data[linear_cords+3] = 255 ; pixels.data[linear_cords + 2] = new_color.b
pixels.data[linear_cords + 3] = 255
if (x > 0) { if (x > 0) {
if( pixelCompare(pixels.data[linear_cords-4], original_color.r) && 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 + 1], original_color.g) &&
pixelCompare(pixels.data[linear_cords-4+2], original_color.b)) { pixelCompare(pixels.data[linear_cords - 4 + 2], original_color.b)
) {
if (!reached_left) { if (!reached_left) {
pixel_stack.push( {x:x-1, y:y} ) ; pixel_stack.push({ x: x - 1, y: y })
reached_left = true ; reached_left = true
} }
} else if (reached_left) { } else if (reached_left) {
reached_left = false ; reached_left = false
} }
} }
if (x < editor.width - 1) { if (x < editor.width - 1) {
if( pixelCompare(pixels.data[linear_cords+4], original_color.r) && 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 + 1], original_color.g) &&
pixelCompare(pixels.data[linear_cords+4+2], original_color.b)) { pixelCompare(pixels.data[linear_cords + 4 + 2], original_color.b)
) {
if (!reached_right) { if (!reached_right) {
pixel_stack.push( {x:x+1,y:y} ) ; pixel_stack.push({ x: x + 1, y: y })
reached_right = true ; reached_right = true
} }
} else if (reached_right) { } else if (reached_right) {
reached_right = false ; reached_right = false
} }
} }
linear_cords += editor.width * 4 ; linear_cords += editor.width * 4
} }
} }
the_canvas_context.putImageData( pixels, 0, 0 ) ; the_canvas_context.putImageData(pixels, 0, 0)
} }

View File

@ -11,56 +11,35 @@
* @type {(() => (string | ImageModalRequest) | string | ImageModalRequest) => {}} * @type {(() => (string | ImageModalRequest) | string | ImageModalRequest) => {}}
*/ */
const imageModal = (function() { const imageModal = (function() {
const backElem = createElement( const backElem = createElement("i", undefined, ["fa-solid", "fa-arrow-left", "tertiaryButton"])
'i',
undefined,
['fa-solid', 'fa-arrow-left', 'tertiaryButton'],
)
const forwardElem = createElement( const forwardElem = createElement("i", undefined, ["fa-solid", "fa-arrow-right", "tertiaryButton"])
'i',
undefined,
['fa-solid', 'fa-arrow-right', 'tertiaryButton'],
)
const zoomElem = createElement( const zoomElem = createElement("i", undefined, ["fa-solid", "tertiaryButton"])
'i',
undefined,
['fa-solid', 'tertiaryButton'],
)
const closeElem = createElement( const closeElem = createElement("i", undefined, ["fa-solid", "fa-xmark", "tertiaryButton"])
'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( const modalElem = createElement("div", { id: "viewFullSizeImgModal" }, ["popup"], [backdrop, modalContainer])
'div',
{ id: 'viewFullSizeImgModal' },
['popup'],
[backdrop, modalContainer],
)
document.body.appendChild(modalElem) document.body.appendChild(modalElem)
const setZoomLevel = (value) => { const setZoomLevel = (value) => {
const img = imageContainer.querySelector('img') const img = imageContainer.querySelector("img")
if (value) { if (value) {
zoomElem.classList.remove('fa-magnifying-glass-plus') zoomElem.classList.remove("fa-magnifying-glass-plus")
zoomElem.classList.add('fa-magnifying-glass-minus') zoomElem.classList.add("fa-magnifying-glass-minus")
if (img) { 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) { if (!zoomLevel) {
zoomLevel = 100 zoomLevel = 100
} }
@ -70,36 +49,35 @@ const imageModal = (function() {
img.height = img.naturalHeight * (+zoomLevel / 100) img.height = img.naturalHeight * (+zoomLevel / 100)
} }
} else { } else {
zoomElem.classList.remove('fa-magnifying-glass-minus') zoomElem.classList.remove("fa-magnifying-glass-minus")
zoomElem.classList.add('fa-magnifying-glass-plus') zoomElem.classList.add("fa-magnifying-glass-plus")
if (img) { if (img) {
img.classList.add('natural-zoom') img.classList.add("natural-zoom")
img.removeAttribute('width') img.removeAttribute("width")
img.removeAttribute('height') img.removeAttribute("height")
} }
} }
} }
zoomElem.addEventListener( zoomElem.addEventListener("click", () =>
'click', setZoomLevel(imageContainer.querySelector("img")?.classList?.contains("natural-zoom"))
() => setZoomLevel(imageContainer.querySelector('img')?.classList?.contains('natural-zoom')),
) )
const state = { const state = {
previous: undefined, previous: undefined,
next: undefined, next: undefined
} }
const clear = () => { const clear = () => {
imageContainer.innerHTML = '' imageContainer.innerHTML = ""
Object.keys(state).forEach(key => delete state[key]) Object.keys(state).forEach((key) => delete state[key])
} }
const close = () => { const close = () => {
clear() clear()
modalElem.classList.remove('active') modalElem.classList.remove("active")
document.body.style.overflow = 'initial' document.body.style.overflow = "initial"
} }
/** /**
@ -113,27 +91,27 @@ const imageModal = (function() {
clear() clear()
const options = typeof optionsFactory === 'function' ? optionsFactory() : optionsFactory const options = typeof optionsFactory === "function" ? optionsFactory() : optionsFactory
const src = typeof options === 'string' ? options : options.src 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) imageContainer.appendChild(imgElem)
modalElem.classList.add('active') modalElem.classList.add("active")
document.body.style.overflow = 'hidden' document.body.style.overflow = "hidden"
setZoomLevel(false) setZoomLevel(false)
if (typeof options === 'object' && options.previous) { if (typeof options === "object" && options.previous) {
state.previous = options.previous state.previous = options.previous
backElem.style.display = 'unset' backElem.style.display = "unset"
} else { } 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 state.next = options.next
forwardElem.style.display = 'unset' forwardElem.style.display = "unset"
} else { } else {
forwardElem.style.display = 'none' forwardElem.style.display = "none"
} }
} }
@ -141,7 +119,7 @@ const imageModal = (function() {
if (state.previous) { if (state.previous) {
init(state.previous) init(state.previous)
} else { } else {
backElem.style.display = 'none' backElem.style.display = "none"
} }
} }
@ -149,27 +127,27 @@ const imageModal = (function() {
if (state.next) { if (state.next) {
init(state.next) init(state.next)
} else { } else {
forwardElem.style.display = 'none' forwardElem.style.display = "none"
} }
} }
window.addEventListener('keydown', (e) => { window.addEventListener("keydown", (e) => {
if (modalElem.classList.contains('active')) { if (modalElem.classList.contains("active")) {
switch (e.key) { switch (e.key) {
case 'Escape': case "Escape":
close() close()
break break
case 'ArrowLeft': case "ArrowLeft":
back() back()
break break
case 'ArrowRight': case "ArrowRight":
forward() forward()
break break
} }
} }
}) })
window.addEventListener('click', (e) => { window.addEventListener("click", (e) => {
if (modalElem.classList.contains('active')) { if (modalElem.classList.contains("active")) {
if (e.target === backdrop || e.target === closeElem) { if (e.target === backdrop || e.target === closeElem) {
close() 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 * @param {() => (string | ImageModalRequest) | string | ImageModalRequest} optionsFactory

View File

@ -3,26 +3,26 @@ let modifiers = []
let customModifiersGroupElement = undefined let customModifiersGroupElement = undefined
let customModifiersInitialContent let customModifiersInitialContent
let editorModifierEntries = document.querySelector('#editor-modifiers-entries') let editorModifierEntries = document.querySelector("#editor-modifiers-entries")
let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list') let editorModifierTagsList = document.querySelector("#editor-inputs-tags-list")
let editorTagsContainer = document.querySelector('#editor-inputs-tags-container') let editorTagsContainer = document.querySelector("#editor-inputs-tags-container")
let modifierCardSizeSlider = document.querySelector('#modifier-card-size-slider') let modifierCardSizeSlider = document.querySelector("#modifier-card-size-slider")
let previewImageField = document.querySelector('#preview-image') let previewImageField = document.querySelector("#preview-image")
let modifierSettingsBtn = document.querySelector('#modifier-settings-btn') let modifierSettingsBtn = document.querySelector("#modifier-settings-btn")
let modifierSettingsOverlay = document.querySelector('#modifier-settings-config') let modifierSettingsOverlay = document.querySelector("#modifier-settings-config")
let customModifiersTextBox = document.querySelector('#custom-modifiers-input') let customModifiersTextBox = document.querySelector("#custom-modifiers-input")
let customModifierEntriesToolbar = document.querySelector('#editor-modifiers-entries-toolbar') let customModifierEntriesToolbar = document.querySelector("#editor-modifiers-entries-toolbar")
const modifierThumbnailPath = 'media/modifier-thumbnails' const modifierThumbnailPath = "media/modifier-thumbnails"
const activeCardClass = 'modifier-card-active' const activeCardClass = "modifier-card-active"
const CUSTOM_MODIFIERS_KEY = "customModifiers" const CUSTOM_MODIFIERS_KEY = "customModifiers"
function createModifierCard(name, previews, removeBy) { function createModifierCard(name, previews, removeBy) {
const modifierCard = document.createElement('div') const modifierCard = document.createElement("div")
let style = previewImageField.value 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 = ` modifierCard.innerHTML = `
<div class="modifier-card-overlay"></div> <div class="modifier-card-overlay"></div>
<div class="modifier-card-image-container"> <div class="modifier-card-image-container">
@ -34,35 +34,35 @@ function createModifierCard(name, previews, removeBy) {
<div class="modifier-card-label"><p></p></div> <div class="modifier-card-label"><p></p></div>
</div>` </div>`
const image = modifierCard.querySelector('.modifier-card-image') const image = modifierCard.querySelector(".modifier-card-image")
const errorText = modifierCard.querySelector('.modifier-card-error-label') const errorText = modifierCard.querySelector(".modifier-card-error-label")
const label = modifierCard.querySelector('.modifier-card-label') const label = modifierCard.querySelector(".modifier-card-label")
errorText.innerText = 'No Image' errorText.innerText = "No Image"
if (typeof previews == 'object') { if (typeof previews == "object") {
image.src = previews[styleIndex]; // portrait image.src = previews[styleIndex] // portrait
image.setAttribute('preview-type', style) image.setAttribute("preview-type", style)
} else { } else {
image.remove() image.remove()
} }
const maxLabelLength = 30 const maxLabelLength = 30
const cardLabel = removeBy ? name.replace('by ', '') : name const cardLabel = removeBy ? name.replace("by ", "") : name
if (cardLabel.length <= maxLabelLength) { if (cardLabel.length <= maxLabelLength) {
label.querySelector('p').innerText = cardLabel label.querySelector("p").innerText = cardLabel
} else { } else {
const tooltipText = document.createElement('span') const tooltipText = document.createElement("span")
tooltipText.className = 'tooltip-text' tooltipText.className = "tooltip-text"
tooltipText.innerText = name tooltipText.innerText = name
label.classList.add('tooltip') label.classList.add("tooltip")
label.appendChild(tooltipText) 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 return modifierCard
} }
@ -71,55 +71,58 @@ function createModifierGroup(modifierGroup, initiallyExpanded, removeBy) {
const title = modifierGroup.category const title = modifierGroup.category
const modifiers = modifierGroup.modifiers const modifiers = modifierGroup.modifiers
const titleEl = document.createElement('h5') const titleEl = document.createElement("h5")
titleEl.className = 'collapsible' titleEl.className = "collapsible"
titleEl.innerText = title titleEl.innerText = title
const modifiersEl = document.createElement('div') const modifiersEl = document.createElement("div")
modifiersEl.classList.add('collapsible-content', 'editor-modifiers-leaf') modifiersEl.classList.add("collapsible-content", "editor-modifiers-leaf")
if (initiallyExpanded === true) { if (initiallyExpanded === true) {
titleEl.className += ' active' titleEl.className += " active"
} }
modifiers.forEach(modObj => { modifiers.forEach((modObj) => {
const modifierName = modObj.modifier 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) const modifierCard = createModifierCard(modifierName, modifierPreviews, removeBy)
if(typeof modifierCard == 'object') { if (typeof modifierCard == "object") {
modifiersEl.appendChild(modifierCard) modifiersEl.appendChild(modifierCard)
const trimmedName = trimModifiers(modifierName) const trimmedName = trimModifiers(modifierName)
modifierCard.addEventListener('click', () => { modifierCard.addEventListener("click", () => {
if (activeTags.map(x => trimModifiers(x.name)).includes(trimmedName)) { if (activeTags.map((x) => trimModifiers(x.name)).includes(trimmedName)) {
// remove modifier from active array // remove modifier from active array
activeTags = activeTags.filter(x => trimModifiers(x.name) != trimmedName) activeTags = activeTags.filter((x) => trimModifiers(x.name) != trimmedName)
toggleCardState(trimmedName, false) toggleCardState(trimmedName, false)
} else { } else {
// add modifier to active array // add modifier to active array
activeTags.push({ activeTags.push({
'name': modifierName, name: modifierName,
'element': modifierCard.cloneNode(true), element: modifierCard.cloneNode(true),
'originElement': modifierCard, originElement: modifierCard,
'previews': modifierPreviews previews: modifierPreviews
}) })
toggleCardState(trimmedName, true) toggleCardState(trimmedName, true)
} }
refreshTagsList() refreshTagsList()
document.dispatchEvent(new Event('refreshImageModifiers')) document.dispatchEvent(new Event("refreshImageModifiers"))
}) })
} }
}) })
let brk = document.createElement('br') let brk = document.createElement("br")
brk.style.clear = 'both' brk.style.clear = "both"
modifiersEl.appendChild(brk) modifiersEl.appendChild(brk)
let e = document.createElement('div') let e = document.createElement("div")
e.className = 'modifier-category' e.className = "modifier-category"
e.appendChild(titleEl) e.appendChild(titleEl)
e.appendChild(modifiersEl) e.appendChild(modifiersEl)
@ -130,87 +133,98 @@ function createModifierGroup(modifierGroup, initiallyExpanded, removeBy) {
function trimModifiers(tag) { function trimModifiers(tag) {
// Remove trailing '-' and/or '+' // Remove trailing '-' and/or '+'
tag = tag.replace(/[-+]+$/, ''); tag = tag.replace(/[-+]+$/, "")
// Remove parentheses at beginning and end // Remove parentheses at beginning and end
return tag.replace(/^[(]+|[\s)]+$/g, ''); return tag.replace(/^[(]+|[\s)]+$/g, "")
} }
async function loadModifiers() { async function loadModifiers() {
try { try {
let res = await fetch('/get/modifiers') let res = await fetch("/get/modifiers")
if (res.status === 200) { if (res.status === 200) {
res = await res.json() res = await res.json()
modifiers = res; // update global variable modifiers = res // update global variable
res.reverse() res.reverse()
res.forEach((modifierGroup, idx) => { 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) createCollapsibles(editorModifierEntries)
} }
} catch (e) { } catch (e) {
console.error('error fetching modifiers', e) console.error("error fetching modifiers", e)
} }
loadCustomModifiers() loadCustomModifiers()
resizeModifierCards(modifierCardSizeSlider.value) resizeModifierCards(modifierCardSizeSlider.value)
document.dispatchEvent(new Event('loadImageModifiers')) document.dispatchEvent(new Event("loadImageModifiers"))
} }
function refreshModifiersState(newTags, inactiveTags) { function refreshModifiersState(newTags, inactiveTags) {
// clear existing modifiers // clear existing modifiers
document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(modifierCard => { document
const modifierName = modifierCard.querySelector('.modifier-card-label p').dataset.fullName // pick the full modifier name .querySelector("#editor-modifiers")
if (activeTags.map(x => x.name).includes(modifierName)) { .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.classList.remove(activeCardClass)
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+' modifierCard.querySelector(".modifier-card-image-overlay").innerText = "+"
} }
}) })
activeTags = [] activeTags = []
// set new modifiers // set new modifiers
newTags.forEach(tag => { newTags.forEach((tag) => {
let found = false let found = false
document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(modifierCard => { document
const modifierName = modifierCard.querySelector('.modifier-card-label p').dataset.fullName .querySelector("#editor-modifiers")
const shortModifierName = modifierCard.querySelector('.modifier-card-label p').innerText .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)) { if (trimModifiers(tag) == trimModifiers(modifierName)) {
// add modifier to active array // 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 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) const imageModifierCard = modifierCard.cloneNode(true)
imageModifierCard.querySelector('.modifier-card-label p').innerText = tag.replace(modifierName, shortModifierName) imageModifierCard.querySelector(".modifier-card-label p").innerText = tag.replace(
modifierName,
shortModifierName
)
activeTags.push({ activeTags.push({
'name': tag, name: tag,
'element': imageModifierCard, element: imageModifierCard,
'originElement': modifierCard originElement: modifierCard
}) })
} }
modifierCard.classList.add(activeCardClass) modifierCard.classList.add(activeCardClass)
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-' modifierCard.querySelector(".modifier-card-image-overlay").innerText = "-"
found = true 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 let modifierCard = createModifierCard(tag, undefined, false) // create a modifier card for the missing tag, no image
modifierCard.addEventListener('click', () => { modifierCard.addEventListener("click", () => {
if (activeTags.map(x => x.name).includes(tag)) { if (activeTags.map((x) => x.name).includes(tag)) {
// remove modifier from active array // remove modifier from active array
activeTags = activeTags.filter(x => x.name != tag) activeTags = activeTags.filter((x) => x.name != tag)
modifierCard.classList.remove(activeCardClass) modifierCard.classList.remove(activeCardClass)
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+' modifierCard.querySelector(".modifier-card-image-overlay").innerText = "+"
} }
refreshTagsList() refreshTagsList()
}) })
activeTags.push({ activeTags.push({
'name': tag, name: tag,
'element': modifierCard, element: modifierCard,
'originElement': undefined // no origin element for missing tags originElement: undefined // no origin element for missing tags
}) })
} }
}) })
@ -220,41 +234,44 @@ function refreshModifiersState(newTags, inactiveTags) {
function refreshInactiveTags(inactiveTags) { function refreshInactiveTags(inactiveTags) {
// update inactive tags // update inactive tags
if (inactiveTags !== undefined && inactiveTags.length > 0) { if (inactiveTags !== undefined && inactiveTags.length > 0) {
activeTags.forEach (tag => { activeTags.forEach((tag) => {
if (inactiveTags.find(element => element === tag.name) !== undefined) { if (inactiveTags.find((element) => element === tag.name) !== undefined) {
tag.inactive = true tag.inactive = true
} }
}) })
} }
// update cards // update cards
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay') let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay")
overlays.forEach (i => { overlays.forEach((i) => {
let modifierName = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].dataset.fullName let modifierName = i.parentElement.getElementsByClassName("modifier-card-label")[0].getElementsByTagName("p")[0]
if (inactiveTags?.find(element => element === modifierName) !== undefined) { .dataset.fullName
i.parentElement.classList.add('modifier-toggle-inactive') if (inactiveTags?.find((element) => element === modifierName) !== undefined) {
i.parentElement.classList.add("modifier-toggle-inactive")
} }
}) })
} }
function refreshTagsList(inactiveTags) { function refreshTagsList(inactiveTags) {
editorModifierTagsList.innerHTML = '' editorModifierTagsList.innerHTML = ""
if (activeTags.length == 0) { if (activeTags.length == 0) {
editorTagsContainer.style.display = 'none' editorTagsContainer.style.display = "none"
return return
} else { } else {
editorTagsContainer.style.display = 'block' editorTagsContainer.style.display = "block"
} }
activeTags.forEach((tag, index) => { activeTags.forEach((tag, index) => {
tag.element.querySelector('.modifier-card-image-overlay').innerText = '-' tag.element.querySelector(".modifier-card-image-overlay").innerText = "-"
tag.element.classList.add('modifier-card-tiny') tag.element.classList.add("modifier-card-tiny")
editorModifierTagsList.appendChild(tag.element) editorModifierTagsList.appendChild(tag.element)
tag.element.addEventListener('click', () => { tag.element.addEventListener("click", () => {
let idx = activeTags.findIndex(o => { return o.name === tag.name }) let idx = activeTags.findIndex((o) => {
return o.name === tag.name
})
if (idx !== -1) { if (idx !== -1) {
toggleCardState(activeTags[idx].name, false) toggleCardState(activeTags[idx].name, false)
@ -262,86 +279,89 @@ function refreshTagsList(inactiveTags) {
activeTags.splice(idx, 1) activeTags.splice(idx, 1)
refreshTagsList() refreshTagsList()
} }
document.dispatchEvent(new Event('refreshImageModifiers')) document.dispatchEvent(new Event("refreshImageModifiers"))
}) })
}) })
let brk = document.createElement('br') let brk = document.createElement("br")
brk.style.clear = 'both' brk.style.clear = "both"
editorModifierTagsList.appendChild(brk) editorModifierTagsList.appendChild(brk)
refreshInactiveTags(inactiveTags) 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) { function toggleCardState(modifierName, makeActive) {
document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(card => { document
const name = card.querySelector('.modifier-card-label').innerText .querySelector("#editor-modifiers")
if ( trimModifiers(modifierName) == trimModifiers(name) .querySelectorAll(".modifier-card")
|| trimModifiers(modifierName) == 'by ' + trimModifiers(name)) { .forEach((card) => {
const name = card.querySelector(".modifier-card-label").innerText
if (
trimModifiers(modifierName) == trimModifiers(name) ||
trimModifiers(modifierName) == "by " + trimModifiers(name)
) {
if (makeActive) { if (makeActive) {
card.classList.add(activeCardClass) card.classList.add(activeCardClass)
card.querySelector('.modifier-card-image-overlay').innerText = '-' card.querySelector(".modifier-card-image-overlay").innerText = "-"
} } else {
else{
card.classList.remove(activeCardClass) card.classList.remove(activeCardClass)
card.querySelector('.modifier-card-image-overlay').innerText = '+' card.querySelector(".modifier-card-image-overlay").innerText = "+"
} }
} }
}) })
} }
function changePreviewImages(val) { function changePreviewImages(val) {
const previewImages = document.querySelectorAll('.modifier-card-image-container img') const previewImages = document.querySelectorAll(".modifier-card-image-container img")
let previewArr = [] let previewArr = []
modifiers.map(x => x.modifiers).forEach(x => previewArr.push(...x.map(m => m.previews))) modifiers.map((x) => x.modifiers).forEach((x) => previewArr.push(...x.map((m) => m.previews)))
previewArr = previewArr.map(x => { previewArr = previewArr.map((x) => {
let obj = {} let obj = {}
x.forEach(preview => { x.forEach((preview) => {
obj[preview.name] = preview.path obj[preview.name] = preview.path
}) })
return obj return obj
}) })
previewImages.forEach(previewImage => { previewImages.forEach((previewImage) => {
const currentPreviewType = previewImage.getAttribute('preview-type') const currentPreviewType = previewImage.getAttribute("preview-type")
const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + '/').pop() 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 let preview = null
if (val == 'portrait') { if (val == "portrait") {
preview = previews.portrait preview = previews.portrait
} } else if (val == "landscape") {
else if (val == 'landscape') {
preview = previews.landscape preview = previews.landscape
} }
if (preview != null) { if (preview != null) {
previewImage.src = `${modifierThumbnailPath}/${preview}` previewImage.src = `${modifierThumbnailPath}/${preview}`
previewImage.setAttribute('preview-type', val) previewImage.setAttribute("preview-type", val)
} }
} }
}) })
} }
function resizeModifierCards(val) { function resizeModifierCards(val) {
const cardSizePrefix = 'modifier-card-size_' const cardSizePrefix = "modifier-card-size_"
const modifierCardClass = 'modifier-card' const modifierCardClass = "modifier-card"
const modifierCards = document.querySelectorAll(`.${modifierCardClass}`) 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 // remove existing size classes
const classes = card.className.split(' ').filter(c => !c.startsWith(cardSizePrefix)) const classes = card.className.split(" ").filter((c) => !c.startsWith(cardSizePrefix))
card.className = classes.join(' ').trim() card.className = classes.join(" ").trim()
if (val != 0) { if (val != 0) {
card.classList.add(cardSize(val)) card.classList.add(cardSize(val))
@ -352,7 +372,7 @@ function resizeModifierCards(val) {
modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value) modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value)
previewImageField.onchange = () => changePreviewImages(previewImageField.value) previewImageField.onchange = () => changePreviewImages(previewImageField.value)
modifierSettingsBtn.addEventListener('click', function(e) { modifierSettingsBtn.addEventListener("click", function(e) {
modifierSettingsOverlay.classList.add("active") modifierSettingsOverlay.classList.add("active")
customModifiersTextBox.setSelectionRange(0, 0) customModifiersTextBox.setSelectionRange(0, 0)
customModifiersTextBox.focus() customModifiersTextBox.focus()
@ -360,7 +380,7 @@ modifierSettingsBtn.addEventListener('click', function(e) {
e.stopPropagation() e.stopPropagation()
}) })
modifierSettingsOverlay.addEventListener('keydown', function(e) { modifierSettingsOverlay.addEventListener("keydown", function(e) {
switch (e.key) { switch (e.key) {
case "Escape": // Escape to cancel case "Escape": // Escape to cancel
customModifiersTextBox.value = customModifiersInitialContent // undo the changes customModifiersTextBox.value = customModifiersInitialContent // undo the changes
@ -368,7 +388,8 @@ modifierSettingsOverlay.addEventListener('keydown', function(e) {
e.stopPropagation() e.stopPropagation()
break break
case "Enter": case "Enter":
if (e.ctrlKey) { // Ctrl+Enter to confirm if (e.ctrlKey) {
// Ctrl+Enter to confirm
modifierSettingsOverlay.classList.remove("active") modifierSettingsOverlay.classList.remove("active")
e.stopPropagation() e.stopPropagation()
break break
@ -383,7 +404,7 @@ function saveCustomModifiers() {
} }
function loadCustomModifiers() { 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)

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,8 @@ var ParameterType = {
select: "select", select: "select",
select_multiple: "select_multiple", select_multiple: "select_multiple",
slider: "slider", slider: "slider",
custom: "custom", custom: "custom"
}; }
/** /**
* JSDoc style * JSDoc style
@ -24,7 +24,6 @@ var ParameterType = {
* @property {boolean?} saveInAppConfig * @property {boolean?} saveInAppConfig
*/ */
/** @type {Array.<Parameter>} */ /** @type {Array.<Parameter>} */
var PARAMETERS = [ var PARAMETERS = [
{ {
@ -33,7 +32,8 @@ var PARAMETERS = [
label: "Theme", label: "Theme",
default: "theme-default", default: "theme-default",
note: "customize the look and feel of the ui", note: "customize the look and feel of the ui",
options: [ // Note: options expanded dynamically options: [
// Note: options expanded dynamically
{ {
value: "theme-default", value: "theme-default",
label: "Default" label: "Default"
@ -47,7 +47,7 @@ var PARAMETERS = [
label: "Auto-Save Images", label: "Auto-Save Images",
note: "automatically saves images to the specified location", note: "automatically saves images to the specified location",
icon: "fa-download", icon: "fa-download",
default: false, default: false
}, },
{ {
id: "diskPath", id: "diskPath",
@ -82,13 +82,13 @@ var PARAMETERS = [
}, },
{ {
value: "embed,txt", value: "embed,txt",
label: "embed & txt", label: "embed & txt"
}, },
{ {
value: "embed,json", value: "embed,json",
label: "embed & json", label: "embed & json"
}, }
], ]
}, },
{ {
id: "block_nsfw", id: "block_nsfw",
@ -96,7 +96,7 @@ var PARAMETERS = [
label: "Block NSFW images", label: "Block NSFW images",
note: "blurs out NSFW images", note: "blurs out NSFW images",
icon: "fa-land-mine-on", icon: "fa-land-mine-on",
default: false, default: false
}, },
{ {
id: "sound_toggle", id: "sound_toggle",
@ -104,7 +104,7 @@ var PARAMETERS = [
label: "Enable Sound", label: "Enable Sound",
note: "plays a sound on task completion", note: "plays a sound on task completion",
icon: "fa-volume-low", icon: "fa-volume-low",
default: true, default: true
}, },
{ {
id: "process_order_toggle", id: "process_order_toggle",
@ -112,7 +112,7 @@ var PARAMETERS = [
label: "Process newest jobs first", label: "Process newest jobs first",
note: "reverse the normal processing order", note: "reverse the normal processing order",
icon: "fa-arrow-down-short-wide", icon: "fa-arrow-down-short-wide",
default: false, default: false
}, },
{ {
id: "ui_open_browser_on_start", id: "ui_open_browser_on_start",
@ -121,13 +121,14 @@ var PARAMETERS = [
note: "starts the default browser on startup", note: "starts the default browser on startup",
icon: "fa-window-restore", icon: "fa-window-restore",
default: true, default: true,
saveInAppConfig: true, saveInAppConfig: true
}, },
{ {
id: "vram_usage_level", id: "vram_usage_level",
type: ParameterType.select, type: ParameterType.select,
label: "GPU Memory Usage", label: "GPU Memory Usage",
note: "Faster performance requires more GPU memory (VRAM)<br/><br/>" + note:
"Faster performance requires more GPU memory (VRAM)<br/><br/>" +
"<b>Balanced:</b> nearly as fast as High, much lower VRAM usage<br/>" + "<b>Balanced:</b> nearly as fast as High, much lower VRAM usage<br/>" +
"<b>High:</b> fastest, maximum GPU memory usage</br>" + "<b>High:</b> fastest, maximum GPU memory usage</br>" +
"<b>Low:</b> slowest, recommended for GPUs with 3 to 4 GB memory", "<b>Low:</b> slowest, recommended for GPUs with 3 to 4 GB memory",
@ -137,7 +138,7 @@ var PARAMETERS = [
{ value: "balanced", label: "Balanced" }, { value: "balanced", label: "Balanced" },
{ value: "high", label: "High" }, { value: "high", label: "High" },
{ value: "low", label: "Low" } { value: "low", label: "Low" }
], ]
}, },
{ {
id: "use_cpu", id: "use_cpu",
@ -145,20 +146,20 @@ var PARAMETERS = [
label: "Use CPU (not GPU)", label: "Use CPU (not GPU)",
note: "warning: this will be *very* slow", note: "warning: this will be *very* slow",
icon: "fa-microchip", icon: "fa-microchip",
default: false, default: false
}, },
{ {
id: "auto_pick_gpus", id: "auto_pick_gpus",
type: ParameterType.checkbox, type: ParameterType.checkbox,
label: "Automatically pick the GPUs (experimental)", label: "Automatically pick the GPUs (experimental)",
default: false, default: false
}, },
{ {
id: "use_gpus", id: "use_gpus",
type: ParameterType.select_multiple, type: ParameterType.select_multiple,
label: "GPUs to use (experimental)", label: "GPUs to use (experimental)",
note: "to process in parallel", note: "to process in parallel",
default: false, default: false
}, },
{ {
id: "auto_save_settings", id: "auto_save_settings",
@ -166,15 +167,16 @@ var PARAMETERS = [
label: "Auto-Save Settings", label: "Auto-Save Settings",
note: "restores settings on browser load", note: "restores settings on browser load",
icon: "fa-gear", icon: "fa-gear",
default: true, default: true
}, },
{ {
id: "confirm_dangerous_actions", id: "confirm_dangerous_actions",
type: ParameterType.checkbox, type: ParameterType.checkbox,
label: "Confirm dangerous actions", label: "Confirm dangerous actions",
note: "Actions that might lead to data loss must either be clicked with the shift key pressed, or confirmed in an 'Are you sure?' dialog", note:
"Actions that might lead to data loss must either be clicked with the shift key pressed, or confirmed in an 'Are you sure?' dialog",
icon: "fa-check-double", icon: "fa-check-double",
default: true, default: true
}, },
{ {
id: "listen_to_network", id: "listen_to_network",
@ -183,7 +185,7 @@ var PARAMETERS = [
note: "Other devices on your network can access this web page", note: "Other devices on your network can access this web page",
icon: "fa-network-wired", icon: "fa-network-wired",
default: true, default: true,
saveInAppConfig: true, saveInAppConfig: true
}, },
{ {
id: "listen_port", id: "listen_port",
@ -194,29 +196,31 @@ var PARAMETERS = [
render: (parameter) => { render: (parameter) => {
return `<input id="${parameter.id}" name="${parameter.id}" size="6" value="9000" onkeypress="preventNonNumericalInput(event)">` return `<input id="${parameter.id}" name="${parameter.id}" size="6" value="9000" onkeypress="preventNonNumericalInput(event)">`
}, },
saveInAppConfig: true, saveInAppConfig: true
}, },
{ {
id: "use_beta_channel", id: "use_beta_channel",
type: ParameterType.checkbox, type: ParameterType.checkbox,
label: "Beta channel", label: "Beta channel",
note: "Get the latest features immediately (but could be less stable). Please restart the program after changing this.", note:
"Get the latest features immediately (but could be less stable). Please restart the program after changing this.",
icon: "fa-fire", icon: "fa-fire",
default: false, default: false
}, },
{ {
id: "test_diffusers", id: "test_diffusers",
type: ParameterType.checkbox, type: ParameterType.checkbox,
label: "Test Diffusers", label: "Test Diffusers",
note: "<b>Experimental! Can have bugs!</b> Use upcoming features (like LoRA) in our new engine. Please press Save, then restart the program after changing this.", note:
"<b>Experimental! Can have bugs!</b> Use upcoming features (like LoRA) in our new engine. Please press Save, then restart the program after changing this.",
icon: "fa-bolt", icon: "fa-bolt",
default: false, default: false,
saveInAppConfig: true, saveInAppConfig: true
}, }
]; ]
function getParameterSettingsEntry(id) { function getParameterSettingsEntry(id) {
let parameter = PARAMETERS.filter(p => p.id === id) let parameter = PARAMETERS.filter((p) => p.id === id)
if (parameter.length === 0) { if (parameter.length === 0) {
return return
} }
@ -224,12 +228,12 @@ function getParameterSettingsEntry(id) {
} }
function sliderUpdate(event) { function sliderUpdate(event) {
if (event.srcElement.id.endsWith('-input')) { if (event.srcElement.id.endsWith("-input")) {
let slider = document.getElementById(event.srcElement.id.slice(0, -6)) let slider = document.getElementById(event.srcElement.id.slice(0, -6))
slider.value = event.srcElement.value slider.value = event.srcElement.value
slider.dispatchEvent(new Event("change")) slider.dispatchEvent(new Event("change"))
} else { } else {
let field = document.getElementById(event.srcElement.id+'-input') let field = document.getElementById(event.srcElement.id + "-input")
field.value = event.srcElement.value field.value = event.srcElement.value
field.dispatchEvent(new Event("change")) field.dispatchEvent(new Event("change"))
} }
@ -242,19 +246,21 @@ function sliderUpdate(event) {
function getParameterElement(parameter) { function getParameterElement(parameter) {
switch (parameter.type) { switch (parameter.type) {
case ParameterType.checkbox: case ParameterType.checkbox:
var is_checked = parameter.default ? " checked" : ""; var is_checked = parameter.default ? " checked" : ""
return `<input id="${parameter.id}" name="${parameter.id}"${is_checked} type="checkbox">` return `<input id="${parameter.id}" name="${parameter.id}"${is_checked} type="checkbox">`
case ParameterType.select: case ParameterType.select:
case ParameterType.select_multiple: case ParameterType.select_multiple:
var options = (parameter.options || []).map(option => `<option value="${option.value}">${option.label}</option>`).join("") var options = (parameter.options || [])
var multiple = (parameter.type == ParameterType.select_multiple ? 'multiple' : '') .map((option) => `<option value="${option.value}">${option.label}</option>`)
.join("")
var multiple = parameter.type == ParameterType.select_multiple ? "multiple" : ""
return `<select id="${parameter.id}" name="${parameter.id}" ${multiple}>${options}</select>` return `<select id="${parameter.id}" name="${parameter.id}" ${multiple}>${options}</select>`
case ParameterType.slider: case ParameterType.slider:
return `<input id="${parameter.id}" name="${parameter.id}" class="editor-slider" type="range" value="${parameter.default}" min="${parameter.slider_min}" max="${parameter.slider_max}" oninput="sliderUpdate(event)"> <input id="${parameter.id}-input" name="${parameter.id}-input" size="4" value="${parameter.default}" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)" oninput="sliderUpdate(event)">&nbsp;${parameter.slider_unit}` return `<input id="${parameter.id}" name="${parameter.id}" class="editor-slider" type="range" value="${parameter.default}" min="${parameter.slider_min}" max="${parameter.slider_max}" oninput="sliderUpdate(event)"> <input id="${parameter.id}-input" name="${parameter.id}-input" size="4" value="${parameter.default}" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)" oninput="sliderUpdate(event)">&nbsp;${parameter.slider_unit}`
case ParameterType.custom: case ParameterType.custom:
return parameter.render(parameter) return parameter.render(parameter)
default: default:
console.error(`Invalid type ${parameter.type} for parameter ${parameter.id}`); console.error(`Invalid type ${parameter.type} for parameter ${parameter.id}`)
return "ERROR: Invalid Type" return "ERROR: Invalid Type"
} }
} }
@ -265,31 +271,31 @@ let parametersTable = document.querySelector("#system-settings .parameters-table
* @param {Array<Parameter> | undefined} parameters * @param {Array<Parameter> | undefined} parameters
* */ * */
function initParameters(parameters) { function initParameters(parameters) {
parameters.forEach(parameter => { parameters.forEach((parameter) => {
const element = getParameterElement(parameter) const element = getParameterElement(parameter)
const elementWrapper = createElement('div') const elementWrapper = createElement("div")
if (element instanceof Node) { if (element instanceof Node) {
elementWrapper.appendChild(element) elementWrapper.appendChild(element)
} else { } else {
elementWrapper.innerHTML = element elementWrapper.innerHTML = element
} }
const note = typeof parameter.note === 'function' ? parameter.note(parameter) : parameter.note const note = typeof parameter.note === "function" ? parameter.note(parameter) : parameter.note
const noteElements = [] const noteElements = []
if (note) { if (note) {
const noteElement = createElement('small') const noteElement = createElement("small")
if (note instanceof Node) { if (note instanceof Node) {
noteElement.appendChild(note) noteElement.appendChild(note)
} else { } else {
noteElement.innerHTML = note || '' noteElement.innerHTML = note || ""
} }
noteElements.push(noteElement) noteElements.push(noteElement)
} }
const icon = parameter.icon ? [createElement('i', undefined, ['fa', parameter.icon])] : [] const icon = parameter.icon ? [createElement("i", undefined, ["fa", parameter.icon])] : []
const label = typeof parameter.label === 'function' ? parameter.label(parameter) : parameter.label const label = typeof parameter.label === "function" ? parameter.label(parameter) : parameter.label
const labelElement = createElement('label', { for: parameter.id }) const labelElement = createElement("label", { for: parameter.id })
if (label instanceof Node) { if (label instanceof Node) {
labelElement.appendChild(label) labelElement.appendChild(label)
} else { } else {
@ -297,13 +303,13 @@ function initParameters(parameters) {
} }
const newrow = createElement( const newrow = createElement(
'div', "div",
{ 'data-setting-id': parameter.id, 'data-save-in-app-config': parameter.saveInAppConfig }, { "data-setting-id": parameter.id, "data-save-in-app-config": parameter.saveInAppConfig },
undefined, undefined,
[ [
createElement('div', undefined, undefined, icon), createElement("div", undefined, undefined, icon),
createElement('div', undefined, undefined, [labelElement, ...noteElements]), createElement("div", undefined, undefined, [labelElement, ...noteElements]),
elementWrapper, elementWrapper
] ]
) )
parametersTable.appendChild(newrow) parametersTable.appendChild(newrow)
@ -314,22 +320,25 @@ function initParameters(parameters) {
initParameters(PARAMETERS) initParameters(PARAMETERS)
// listen to parameters from plugins // listen to parameters from plugins
PARAMETERS.addEventListener('push', (...items) => { PARAMETERS.addEventListener("push", (...items) => {
initParameters(items) initParameters(items)
if (items.find(item => item.saveInAppConfig)) { if (items.find((item) => item.saveInAppConfig)) {
console.log('Reloading app config for new parameters', items.map(p => p.id)) console.log(
"Reloading app config for new parameters",
items.map((p) => p.id)
)
getAppConfig() getAppConfig()
} }
}) })
let vramUsageLevelField = document.querySelector('#vram_usage_level') let vramUsageLevelField = document.querySelector("#vram_usage_level")
let useCPUField = document.querySelector('#use_cpu') let useCPUField = document.querySelector("#use_cpu")
let autoPickGPUsField = document.querySelector('#auto_pick_gpus') let autoPickGPUsField = document.querySelector("#auto_pick_gpus")
let useGPUsField = document.querySelector('#use_gpus') let useGPUsField = document.querySelector("#use_gpus")
let saveToDiskField = document.querySelector('#save_to_disk') let saveToDiskField = document.querySelector("#save_to_disk")
let diskPathField = document.querySelector('#diskPath') let diskPathField = document.querySelector("#diskPath")
let metadataOutputFormatField = document.querySelector('#metadata_output_format') let metadataOutputFormatField = document.querySelector("#metadata_output_format")
let listenToNetworkField = document.querySelector("#listen_to_network") let listenToNetworkField = document.querySelector("#listen_to_network")
let listenPortField = document.querySelector("#listen_port") let listenPortField = document.querySelector("#listen_port")
let useBetaChannelField = document.querySelector("#use_beta_channel") let useBetaChannelField = document.querySelector("#use_beta_channel")
@ -337,35 +346,34 @@ let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_star
let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions") let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions")
let testDiffusers = document.querySelector("#test_diffusers") let testDiffusers = document.querySelector("#test_diffusers")
let saveSettingsBtn = document.querySelector('#save-system-settings-btn') let saveSettingsBtn = document.querySelector("#save-system-settings-btn")
async function changeAppConfig(configDelta) { async function changeAppConfig(configDelta) {
try { try {
let res = await fetch('/app_config', { let res = await fetch("/app_config", {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json"
}, },
body: JSON.stringify(configDelta) body: JSON.stringify(configDelta)
}) })
res = await res.json() res = await res.json()
console.log('set config status response', res) console.log("set config status response", res)
} catch (e) { } catch (e) {
console.log('set config status error', e) console.log("set config status error", e)
} }
} }
async function getAppConfig() { async function getAppConfig() {
try { try {
let res = await fetch('/get/app_config') let res = await fetch("/get/app_config")
const config = await res.json() const config = await res.json()
applySettingsFromConfig(config) applySettingsFromConfig(config)
// custom overrides // custom overrides
if (config.update_branch === 'beta') { if (config.update_branch === "beta") {
useBetaChannelField.checked = true useBetaChannelField.checked = true
document.querySelector("#updateBranchLabel").innerText = "(beta)" document.querySelector("#updateBranchLabel").innerText = "(beta)"
} else { } else {
@ -380,45 +388,48 @@ async function getAppConfig() {
if (config.net && config.net.listen_port !== undefined) { if (config.net && config.net.listen_port !== undefined) {
listenPortField.value = config.net.listen_port listenPortField.value = config.net.listen_port
} }
if (config.test_diffusers === undefined || config.update_branch === 'main') { if (config.test_diffusers === undefined || config.update_branch === "main") {
testDiffusers.checked = false testDiffusers.checked = false
document.querySelector("#lora_model_container").style.display = 'none' document.querySelector("#lora_model_container").style.display = "none"
document.querySelector("#lora_alpha_container").style.display = 'none' document.querySelector("#lora_alpha_container").style.display = "none"
} else { } else {
testDiffusers.checked = config.test_diffusers && config.update_branch !== 'main' testDiffusers.checked = config.test_diffusers && config.update_branch !== "main"
document.querySelector("#lora_model_container").style.display = (testDiffusers.checked ? '' : 'none') document.querySelector("#lora_model_container").style.display = testDiffusers.checked ? "" : "none"
document.querySelector("#lora_alpha_container").style.display = (testDiffusers.checked && loraModelField.value !== "" ? '' : 'none') document.querySelector("#lora_alpha_container").style.display =
testDiffusers.checked && loraModelField.value !== "" ? "" : "none"
} }
console.log('get config status response', config) console.log("get config status response", config)
return config return config
} catch (e) { } catch (e) {
console.log('get config status error', e) console.log("get config status error", e)
return {} return {}
} }
} }
function applySettingsFromConfig(config) { function applySettingsFromConfig(config) {
Array.from(parametersTable.children).forEach(parameterRow => { Array.from(parametersTable.children).forEach((parameterRow) => {
if (parameterRow.dataset.settingId in config && parameterRow.dataset.saveInAppConfig === 'true') { if (parameterRow.dataset.settingId in config && parameterRow.dataset.saveInAppConfig === "true") {
const configValue = config[parameterRow.dataset.settingId] const configValue = config[parameterRow.dataset.settingId]
const parameterElement = document.getElementById(parameterRow.dataset.settingId) || const parameterElement =
parameterRow.querySelector('input') || parameterRow.querySelector('select') document.getElementById(parameterRow.dataset.settingId) ||
parameterRow.querySelector("input") ||
parameterRow.querySelector("select")
switch (parameterElement?.tagName) { switch (parameterElement?.tagName) {
case 'INPUT': case "INPUT":
if (parameterElement.type === 'checkbox') { if (parameterElement.type === "checkbox") {
parameterElement.checked = configValue parameterElement.checked = configValue
} else { } else {
parameterElement.value = configValue parameterElement.value = configValue
} }
parameterElement.dispatchEvent(new Event('change')) parameterElement.dispatchEvent(new Event("change"))
break break
case 'SELECT': case "SELECT":
if (Array.isArray(configValue)) { if (Array.isArray(configValue)) {
Array.from(parameterElement.options).forEach(option => { Array.from(parameterElement.options).forEach((option) => {
if (configValue.includes(option.value || option.text)) { if (configValue.includes(option.value || option.text)) {
option.selected = true option.selected = true
} }
@ -426,82 +437,85 @@ function applySettingsFromConfig(config) {
} else { } else {
parameterElement.value = configValue parameterElement.value = configValue
} }
parameterElement.dispatchEvent(new Event('change')) parameterElement.dispatchEvent(new Event("change"))
break break
} }
} }
}) })
} }
saveToDiskField.addEventListener('change', function(e) { saveToDiskField.addEventListener("change", function(e) {
diskPathField.disabled = !this.checked diskPathField.disabled = !this.checked
metadataOutputFormatField.disabled = !this.checked metadataOutputFormatField.disabled = !this.checked
}) })
function getCurrentRenderDeviceSelection() { function getCurrentRenderDeviceSelection() {
let selectedGPUs = $('#use_gpus').val() let selectedGPUs = $("#use_gpus").val()
if (useCPUField.checked && !autoPickGPUsField.checked) { if (useCPUField.checked && !autoPickGPUsField.checked) {
return 'cpu' return "cpu"
} }
if (autoPickGPUsField.checked || selectedGPUs.length == 0) { if (autoPickGPUsField.checked || selectedGPUs.length == 0) {
return 'auto' return "auto"
} }
return selectedGPUs.join(',') return selectedGPUs.join(",")
} }
useCPUField.addEventListener('click', function() { useCPUField.addEventListener("click", function() {
let gpuSettingEntry = getParameterSettingsEntry('use_gpus') let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus') let autoPickGPUSettingEntry = getParameterSettingsEntry("auto_pick_gpus")
if (this.checked) { if (this.checked) {
gpuSettingEntry.style.display = 'none' gpuSettingEntry.style.display = "none"
autoPickGPUSettingEntry.style.display = 'none' autoPickGPUSettingEntry.style.display = "none"
autoPickGPUsField.setAttribute('data-old-value', autoPickGPUsField.checked) autoPickGPUsField.setAttribute("data-old-value", autoPickGPUsField.checked)
autoPickGPUsField.checked = false autoPickGPUsField.checked = false
} else if (useGPUsField.options.length >= MIN_GPUS_TO_SHOW_SELECTION) { } else if (useGPUsField.options.length >= MIN_GPUS_TO_SHOW_SELECTION) {
gpuSettingEntry.style.display = '' gpuSettingEntry.style.display = ""
autoPickGPUSettingEntry.style.display = '' autoPickGPUSettingEntry.style.display = ""
let oldVal = autoPickGPUsField.getAttribute('data-old-value') let oldVal = autoPickGPUsField.getAttribute("data-old-value")
if (oldVal === null || oldVal === undefined) { // the UI started with CPU selected by default if (oldVal === null || oldVal === undefined) {
// the UI started with CPU selected by default
autoPickGPUsField.checked = true autoPickGPUsField.checked = true
} else { } else {
autoPickGPUsField.checked = (oldVal === 'true') autoPickGPUsField.checked = oldVal === "true"
} }
gpuSettingEntry.style.display = (autoPickGPUsField.checked ? 'none' : '') gpuSettingEntry.style.display = autoPickGPUsField.checked ? "none" : ""
} }
}) })
useGPUsField.addEventListener('click', function() { useGPUsField.addEventListener("click", function() {
let selectedGPUs = $('#use_gpus').val() let selectedGPUs = $("#use_gpus").val()
autoPickGPUsField.checked = (selectedGPUs.length === 0) autoPickGPUsField.checked = selectedGPUs.length === 0
}) })
autoPickGPUsField.addEventListener('click', function() { autoPickGPUsField.addEventListener("click", function() {
if (this.checked) { if (this.checked) {
$('#use_gpus').val([]) $("#use_gpus").val([])
} }
let gpuSettingEntry = getParameterSettingsEntry('use_gpus') let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
gpuSettingEntry.style.display = (this.checked ? 'none' : '') gpuSettingEntry.style.display = this.checked ? "none" : ""
}) })
async function setDiskPath(defaultDiskPath, force = false) { async function setDiskPath(defaultDiskPath, force = false) {
var diskPath = getSetting("diskPath") var diskPath = getSetting("diskPath")
if (force || diskPath == '' || diskPath == undefined || diskPath == "undefined") { if (force || diskPath == "" || diskPath == undefined || diskPath == "undefined") {
setSetting("diskPath", defaultDiskPath) setSetting("diskPath", defaultDiskPath)
} }
} }
function setDeviceInfo(devices) { function setDeviceInfo(devices) {
let cpu = devices.all.cpu.name let cpu = devices.all.cpu.name
let allGPUs = Object.keys(devices.all).filter(d => d != 'cpu') let allGPUs = Object.keys(devices.all).filter((d) => d != "cpu")
let activeGPUs = Object.keys(devices.active) let activeGPUs = Object.keys(devices.active)
function ID_TO_TEXT(d) { function ID_TO_TEXT(d) {
let info = devices.all[d] let info = devices.all[d]
if ("mem_free" in info && "mem_total" in info) { if ("mem_free" in info && "mem_total" in info) {
return `${info.name} <small>(${d}) (${info.mem_free.toFixed(1)}Gb free / ${info.mem_total.toFixed(1)} Gb total)</small>` return `${info.name} <small>(${d}) (${info.mem_free.toFixed(1)}Gb free / ${info.mem_total.toFixed(
1
)} Gb total)</small>`
} else { } else {
return `${info.name} <small>(${d}) (no memory info)</small>` return `${info.name} <small>(${d}) (no memory info)</small>`
} }
@ -510,35 +524,35 @@ function setDeviceInfo(devices) {
allGPUs = allGPUs.map(ID_TO_TEXT) allGPUs = allGPUs.map(ID_TO_TEXT)
activeGPUs = activeGPUs.map(ID_TO_TEXT) activeGPUs = activeGPUs.map(ID_TO_TEXT)
let systemInfoEl = document.querySelector('#system-info') let systemInfoEl = document.querySelector("#system-info")
systemInfoEl.querySelector('#system-info-cpu').innerText = cpu systemInfoEl.querySelector("#system-info-cpu").innerText = cpu
systemInfoEl.querySelector('#system-info-gpus-all').innerHTML = allGPUs.join('</br>') systemInfoEl.querySelector("#system-info-gpus-all").innerHTML = allGPUs.join("</br>")
systemInfoEl.querySelector('#system-info-rendering-devices').innerHTML = activeGPUs.join('</br>') systemInfoEl.querySelector("#system-info-rendering-devices").innerHTML = activeGPUs.join("</br>")
} }
function setHostInfo(hosts) { function setHostInfo(hosts) {
let port = listenPortField.value let port = listenPortField.value
hosts = hosts.map(addr => `http://${addr}:${port}/`).map(url => `<div><a href="${url}">${url}</a></div>`) hosts = hosts.map((addr) => `http://${addr}:${port}/`).map((url) => `<div><a href="${url}">${url}</a></div>`)
document.querySelector('#system-info-server-hosts').innerHTML = hosts.join('') document.querySelector("#system-info-server-hosts").innerHTML = hosts.join("")
} }
async function getSystemInfo() { async function getSystemInfo() {
try { try {
const res = await SD.getSystemInfo() const res = await SD.getSystemInfo()
let devices = res['devices'] let devices = res["devices"]
let allDeviceIds = Object.keys(devices['all']).filter(d => d !== 'cpu') let allDeviceIds = Object.keys(devices["all"]).filter((d) => d !== "cpu")
let activeDeviceIds = Object.keys(devices['active']).filter(d => d !== 'cpu') let activeDeviceIds = Object.keys(devices["active"]).filter((d) => d !== "cpu")
if (activeDeviceIds.length === 0) { if (activeDeviceIds.length === 0) {
useCPUField.checked = true useCPUField.checked = true
} }
if (allDeviceIds.length < MIN_GPUS_TO_SHOW_SELECTION || useCPUField.checked) { if (allDeviceIds.length < MIN_GPUS_TO_SHOW_SELECTION || useCPUField.checked) {
let gpuSettingEntry = getParameterSettingsEntry('use_gpus') let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
gpuSettingEntry.style.display = 'none' gpuSettingEntry.style.display = "none"
let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus') let autoPickGPUSettingEntry = getParameterSettingsEntry("auto_pick_gpus")
autoPickGPUSettingEntry.style.display = 'none' autoPickGPUSettingEntry.style.display = "none"
} }
if (allDeviceIds.length === 0) { if (allDeviceIds.length === 0) {
@ -546,27 +560,27 @@ async function getSystemInfo() {
useCPUField.disabled = true // no compatible GPUs, so make the CPU mandatory useCPUField.disabled = true // no compatible GPUs, so make the CPU mandatory
} }
autoPickGPUsField.checked = (devices['config'] === 'auto') autoPickGPUsField.checked = devices["config"] === "auto"
useGPUsField.innerHTML = '' useGPUsField.innerHTML = ""
allDeviceIds.forEach(device => { allDeviceIds.forEach((device) => {
let deviceName = devices['all'][device]['name'] let deviceName = devices["all"][device]["name"]
let deviceOption = `<option value="${device}">${deviceName} (${device})</option>` let deviceOption = `<option value="${device}">${deviceName} (${device})</option>`
useGPUsField.insertAdjacentHTML('beforeend', deviceOption) useGPUsField.insertAdjacentHTML("beforeend", deviceOption)
}) })
if (autoPickGPUsField.checked) { if (autoPickGPUsField.checked) {
let gpuSettingEntry = getParameterSettingsEntry('use_gpus') let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
gpuSettingEntry.style.display = 'none' gpuSettingEntry.style.display = "none"
} else { } else {
$('#use_gpus').val(activeDeviceIds) $("#use_gpus").val(activeDeviceIds)
} }
setDeviceInfo(devices) setDeviceInfo(devices)
setHostInfo(res['hosts']) setHostInfo(res["hosts"])
let force = false let force = false
if (res['enforce_output_dir'] !== undefined) { if (res["enforce_output_dir"] !== undefined) {
force = res['enforce_output_dir'] force = res["enforce_output_dir"]
if (force == true) { if (force == true) {
saveToDiskField.checked = true saveToDiskField.checked = true
metadataOutputFormatField.disabled = false metadataOutputFormatField.disabled = false
@ -574,58 +588,62 @@ async function getSystemInfo() {
saveToDiskField.disabled = force saveToDiskField.disabled = force
diskPathField.disabled = force diskPathField.disabled = force
} }
setDiskPath(res['default_output_dir'], force) setDiskPath(res["default_output_dir"], force)
} catch (e) { } catch (e) {
console.log('error fetching devices', e) console.log("error fetching devices", e)
} }
} }
saveSettingsBtn.addEventListener('click', function() { saveSettingsBtn.addEventListener("click", function() {
if (listenPortField.value == '') { if (listenPortField.value == "") {
alert('The network port field must not be empty.') alert("The network port field must not be empty.")
return return
} }
if (listenPortField.value < 1 || listenPortField.value > 65535) { if (listenPortField.value < 1 || listenPortField.value > 65535) {
alert('The network port must be a number from 1 to 65535') alert("The network port must be a number from 1 to 65535")
return return
} }
const updateBranch = (useBetaChannelField.checked ? 'beta' : 'main') const updateBranch = useBetaChannelField.checked ? "beta" : "main"
const updateAppConfigRequest = { const updateAppConfigRequest = {
'render_devices': getCurrentRenderDeviceSelection(), render_devices: getCurrentRenderDeviceSelection(),
'update_branch': updateBranch, update_branch: updateBranch
} }
Array.from(parametersTable.children).forEach(parameterRow => { Array.from(parametersTable.children).forEach((parameterRow) => {
if (parameterRow.dataset.saveInAppConfig === 'true') { if (parameterRow.dataset.saveInAppConfig === "true") {
const parameterElement = document.getElementById(parameterRow.dataset.settingId) || const parameterElement =
parameterRow.querySelector('input') || parameterRow.querySelector('select') document.getElementById(parameterRow.dataset.settingId) ||
parameterRow.querySelector("input") ||
parameterRow.querySelector("select")
switch (parameterElement?.tagName) { switch (parameterElement?.tagName) {
case 'INPUT': case "INPUT":
if (parameterElement.type === 'checkbox') { if (parameterElement.type === "checkbox") {
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.checked updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.checked
} else { } else {
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value
} }
break break
case 'SELECT': case "SELECT":
if (parameterElement.multiple) { if (parameterElement.multiple) {
updateAppConfigRequest[parameterRow.dataset.settingId] = Array.from(parameterElement.options) updateAppConfigRequest[parameterRow.dataset.settingId] = Array.from(parameterElement.options)
.filter(option => option.selected) .filter((option) => option.selected)
.map(option => option.value || option.text) .map((option) => option.value || option.text)
} else { } else {
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value
} }
break break
default: default:
console.error(`Setting parameter ${parameterRow.dataset.settingId} couldn't be saved to app.config - element #${parameter.id} is a <${parameterElement?.tagName} /> instead of a <input /> or a <select />!`) console.error(
`Setting parameter ${parameterRow.dataset.settingId} couldn't be saved to app.config - element #${parameter.id} is a <${parameterElement?.tagName} /> instead of a <input /> or a <select />!`
)
break break
} }
} }
}) })
const savePromise = changeAppConfig(updateAppConfigRequest) const savePromise = changeAppConfig(updateAppConfigRequest)
saveSettingsBtn.classList.add('active') saveSettingsBtn.classList.add("active")
Promise.all([savePromise, asyncDelay(300)]).then(() => saveSettingsBtn.classList.remove('active')) Promise.all([savePromise, asyncDelay(300)]).then(() => saveSettingsBtn.classList.remove("active"))
}) })

View File

@ -29,14 +29,20 @@ const PLUGINS = {
MODIFIERS_LOAD: [], MODIFIERS_LOAD: [],
TASK_CREATE: [], TASK_CREATE: [],
OUTPUTS_FORMATS: new ServiceContainer( OUTPUTS_FORMATS: new ServiceContainer(
function png() { return (reqBody) => new SD.RenderTask(reqBody) } function png() {
, function jpeg() { return (reqBody) => new SD.RenderTask(reqBody) } return (reqBody) => new SD.RenderTask(reqBody)
, function webp() { return (reqBody) => new SD.RenderTask(reqBody) } },
), function jpeg() {
return (reqBody) => new SD.RenderTask(reqBody)
},
function webp() {
return (reqBody) => new SD.RenderTask(reqBody)
}
)
} }
PLUGINS.OUTPUTS_FORMATS.register = function(...args) { PLUGINS.OUTPUTS_FORMATS.register = function(...args) {
const service = ServiceContainer.prototype.register.apply(this, args) const service = ServiceContainer.prototype.register.apply(this, args)
if (typeof outputFormatField !== 'undefined') { if (typeof outputFormatField !== "undefined") {
const newOption = document.createElement("option") const newOption = document.createElement("option")
newOption.setAttribute("value", service.name) newOption.setAttribute("value", service.name)
newOption.innerText = service.name newOption.innerText = service.name
@ -46,13 +52,13 @@ PLUGINS.OUTPUTS_FORMATS.register = function(...args) {
} }
function loadScript(url) { function loadScript(url) {
const script = document.createElement('script') const script = document.createElement("script")
const promiseSrc = new PromiseSource() const promiseSrc = new PromiseSource()
script.addEventListener('error', () => promiseSrc.reject(new Error(`Script "${url}" couldn't be loaded.`))) script.addEventListener("error", () => promiseSrc.reject(new Error(`Script "${url}" couldn't be loaded.`)))
script.addEventListener('load', () => promiseSrc.resolve(url)) script.addEventListener("load", () => promiseSrc.resolve(url))
script.src = url + '?t=' + Date.now() script.src = url + "?t=" + Date.now()
console.log('loading script', url) console.log("loading script", url)
document.head.appendChild(script) document.head.appendChild(script)
return promiseSrc.promise return promiseSrc.promise
@ -60,7 +66,7 @@ function loadScript(url) {
async function loadUIPlugins() { async function loadUIPlugins() {
try { try {
const res = await fetch('/get/ui_plugins') const res = await fetch("/get/ui_plugins")
if (!res.ok) { if (!res.ok) {
console.error(`Error HTTP${res.status} while loading plugins list. - ${res.statusText}`) console.error(`Error HTTP${res.status} while loading plugins list. - ${res.statusText}`)
return return
@ -69,6 +75,6 @@ async function loadUIPlugins() {
const loadingPromises = plugins.map(loadScript) const loadingPromises = plugins.map(loadScript)
return await Promise.allSettled(loadingPromises) return await Promise.allSettled(loadingPromises)
} catch (e) { } catch (e) {
console.log('error fetching plugin paths', e) console.log("error fetching plugin paths", e)
} }
} }

View File

@ -21,8 +21,7 @@ let hypernetworkModelField = new ModelDropdown(document.querySelector('#hypernet
3) Model dropdowns will be refreshed automatically when the reload models button is invoked. 3) Model dropdowns will be refreshed automatically when the reload models button is invoked.
*/ */
class ModelDropdown class ModelDropdown {
{
modelFilter //= document.querySelector("#model-filter") modelFilter //= document.querySelector("#model-filter")
modelFilterArrow //= document.querySelector("#model-filter-arrow") modelFilterArrow //= document.querySelector("#model-filter-arrow")
modelList //= document.querySelector("#model-list") modelList //= document.querySelector("#model-list")
@ -59,11 +58,11 @@ class ModelDropdown
set disabled(state) { set disabled(state) {
this.modelFilter.disabled = state this.modelFilter.disabled = state
if (this.modelFilterArrow) { if (this.modelFilterArrow) {
this.modelFilterArrow.style.color = state ? 'dimgray' : '' this.modelFilterArrow.style.color = state ? "dimgray" : ""
} }
} }
get modelElements() { get modelElements() {
return this.modelList.querySelectorAll('.model-file') return this.modelList.querySelectorAll(".model-file")
} }
addEventListener(type, listener, options) { addEventListener(type, listener, options) {
return this.modelFilter.addEventListener(type, listener, options) return this.modelFilter.addEventListener(type, listener, options)
@ -83,20 +82,25 @@ class ModelDropdown
} }
/* SEARCHABLE INPUT */ /* SEARCHABLE INPUT */
constructor (input, modelKey, noneEntry = '') {
constructor(input, modelKey, noneEntry = "") {
this.modelFilter = input this.modelFilter = input
this.noneEntry = noneEntry this.noneEntry = noneEntry
this.modelKey = modelKey this.modelKey = modelKey
if (modelsOptions !== undefined) { // reuse models from cache (only useful for plugins, which are loaded after models) if (modelsOptions !== undefined) {
// reuse models from cache (only useful for plugins, which are loaded after models)
this.inputModels = modelsOptions[this.modelKey] this.inputModels = modelsOptions[this.modelKey]
this.populateModels() this.populateModels()
} }
document.addEventListener("refreshModels", this.bind(function(e) { document.addEventListener(
"refreshModels",
this.bind(function(e) {
// reload the models // reload the models
this.inputModels = modelsOptions[this.modelKey] this.inputModels = modelsOptions[this.modelKey]
this.populateModels() this.populateModels()
}, this)) }, this)
)
} }
saveCurrentSelection(elem, value, path) { saveCurrentSelection(elem, value, path) {
@ -105,13 +109,13 @@ class ModelDropdown
this.currentSelection.path = path this.currentSelection.path = path
this.modelFilter.dataset.path = path this.modelFilter.dataset.path = path
this.modelFilter.value = value this.modelFilter.value = value
this.modelFilter.dispatchEvent(new Event('change')) this.modelFilter.dispatchEvent(new Event("change"))
} }
processClick(e) { processClick(e) {
e.preventDefault() e.preventDefault()
if (e.srcElement.classList.contains('model-file') || e.srcElement.classList.contains('fa-file')) { if (e.srcElement.classList.contains("model-file") || e.srcElement.classList.contains("fa-file")) {
const elem = e.srcElement.classList.contains('model-file') ? e.srcElement : e.srcElement.parentElement const elem = e.srcElement.classList.contains("model-file") ? e.srcElement : e.srcElement.parentElement
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path) this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
this.hideModelList() this.hideModelList()
this.modelFilter.focus() this.modelFilter.focus()
@ -126,35 +130,38 @@ class ModelDropdown
return undefined return undefined
} }
return modelElements.slice(0, index).reverse().find(e => e.style.display === 'list-item') return modelElements
.slice(0, index)
.reverse()
.find((e) => e.style.display === "list-item")
} }
getLastVisibleChild(elem) { getLastVisibleChild(elem) {
let lastElementChild = elem.lastElementChild let lastElementChild = elem.lastElementChild
if (lastElementChild.style.display == 'list-item') return lastElementChild if (lastElementChild.style.display == "list-item") return lastElementChild
return this.getPreviousVisibleSibling(lastElementChild) return this.getPreviousVisibleSibling(lastElementChild)
} }
getNextVisibleSibling(elem) { getNextVisibleSibling(elem) {
const modelElements = Array.from(this.modelElements) const modelElements = Array.from(this.modelElements)
const index = modelElements.indexOf(elem) const index = modelElements.indexOf(elem)
return modelElements.slice(index + 1).find(e => e.style.display === 'list-item') return modelElements.slice(index + 1).find((e) => e.style.display === "list-item")
} }
getFirstVisibleChild(elem) { getFirstVisibleChild(elem) {
let firstElementChild = elem.firstElementChild let firstElementChild = elem.firstElementChild
if (firstElementChild.style.display == 'list-item') return firstElementChild if (firstElementChild.style.display == "list-item") return firstElementChild
return this.getNextVisibleSibling(firstElementChild) return this.getNextVisibleSibling(firstElementChild)
} }
selectModelEntry(elem) { selectModelEntry(elem) {
if (elem) { if (elem) {
if (this.highlightedModelEntry !== undefined) { if (this.highlightedModelEntry !== undefined) {
this.highlightedModelEntry.classList.remove('selected') this.highlightedModelEntry.classList.remove("selected")
} }
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path) this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
elem.classList.add('selected') elem.classList.add("selected")
elem.scrollIntoView({block: 'nearest'}) elem.scrollIntoView({ block: "nearest" })
this.highlightedModelEntry = elem this.highlightedModelEntry = elem
} }
} }
@ -163,11 +170,9 @@ class ModelDropdown
const elem = this.getPreviousVisibleSibling(this.highlightedModelEntry) const elem = this.getPreviousVisibleSibling(this.highlightedModelEntry)
if (elem) { if (elem) {
this.selectModelEntry(elem) this.selectModelEntry(elem)
} } else {
else
{
//this.highlightedModelEntry.parentElement.parentElement.scrollIntoView({block: 'nearest'}) //this.highlightedModelEntry.parentElement.parentElement.scrollIntoView({block: 'nearest'})
this.highlightedModelEntry.closest('.model-list').scrollTop = 0 this.highlightedModelEntry.closest(".model-list").scrollTop = 0
} }
this.modelFilter.select() this.modelFilter.select()
} }
@ -178,13 +183,13 @@ class ModelDropdown
} }
selectFirstFile() { selectFirstFile() {
this.selectModelEntry(this.modelList.querySelector('.model-file')) this.selectModelEntry(this.modelList.querySelector(".model-file"))
this.highlightedModelEntry.scrollIntoView({block: 'nearest'}) this.highlightedModelEntry.scrollIntoView({ block: "nearest" })
this.modelFilter.select() this.modelFilter.select()
} }
selectLastFile() { selectLastFile() {
const elems = this.modelList.querySelectorAll('.model-file:last-child') const elems = this.modelList.querySelectorAll(".model-file:last-child")
this.selectModelEntry(elems[elems.length - 1]) this.selectModelEntry(elems[elems.length - 1])
this.modelFilter.select() this.modelFilter.select()
} }
@ -198,57 +203,57 @@ class ModelDropdown
} }
validEntrySelected() { validEntrySelected() {
return (this.modelNoResult.style.display === 'none') return this.modelNoResult.style.display === "none"
} }
processKey(e) { processKey(e) {
switch (e.key) { switch (e.key) {
case 'Escape': case "Escape":
e.preventDefault() e.preventDefault()
this.resetSelection() this.resetSelection()
break break
case 'Enter': case "Enter":
e.preventDefault() e.preventDefault()
if (this.validEntrySelected()) { if (this.validEntrySelected()) {
if (this.modelList.style.display != 'block') { if (this.modelList.style.display != "block") {
this.showModelList() this.showModelList()
} } else {
else this.saveCurrentSelection(
{ this.highlightedModelEntry,
this.saveCurrentSelection(this.highlightedModelEntry, this.highlightedModelEntry.innerText, this.highlightedModelEntry.dataset.path) this.highlightedModelEntry.innerText,
this.highlightedModelEntry.dataset.path
)
this.hideModelList() this.hideModelList()
this.showAllEntries() this.showAllEntries()
} }
this.modelFilter.focus() this.modelFilter.focus()
} } else {
else
{
this.resetSelection() this.resetSelection()
} }
break break
case 'ArrowUp': case "ArrowUp":
e.preventDefault() e.preventDefault()
if (this.validEntrySelected()) { if (this.validEntrySelected()) {
this.selectPreviousFile() this.selectPreviousFile()
} }
break break
case 'ArrowDown': case "ArrowDown":
e.preventDefault() e.preventDefault()
if (this.validEntrySelected()) { if (this.validEntrySelected()) {
this.selectNextFile() this.selectNextFile()
} }
break break
case 'ArrowLeft': case "ArrowLeft":
if (this.modelList.style.display != 'block') { if (this.modelList.style.display != "block") {
e.preventDefault() e.preventDefault()
} }
break break
case 'ArrowRight': case "ArrowRight":
if (this.modelList.style.display != 'block') { if (this.modelList.style.display != "block") {
e.preventDefault() e.preventDefault()
} }
break break
case 'PageUp': case "PageUp":
e.preventDefault() e.preventDefault()
if (this.validEntrySelected()) { if (this.validEntrySelected()) {
this.selectPreviousFile() this.selectPreviousFile()
@ -261,7 +266,7 @@ class ModelDropdown
this.selectPreviousFile() this.selectPreviousFile()
} }
break break
case 'PageDown': case "PageDown":
e.preventDefault() e.preventDefault()
if (this.validEntrySelected()) { if (this.validEntrySelected()) {
this.selectNextFile() this.selectNextFile()
@ -274,7 +279,7 @@ class ModelDropdown
this.selectNextFile() this.selectNextFile()
} }
break break
case 'Home': case "Home":
//if (this.modelList.style.display != 'block') { //if (this.modelList.style.display != 'block') {
e.preventDefault() e.preventDefault()
if (this.validEntrySelected()) { if (this.validEntrySelected()) {
@ -282,7 +287,7 @@ class ModelDropdown
} }
//} //}
break break
case 'End': case "End":
//if (this.modelList.style.display != 'block') { //if (this.modelList.style.display != 'block') {
e.preventDefault() e.preventDefault()
if (this.validEntrySelected()) { if (this.validEntrySelected()) {
@ -301,29 +306,27 @@ class ModelDropdown
} }
showModelList() { showModelList() {
this.modelList.style.display = 'block' this.modelList.style.display = "block"
this.selectEntry() this.selectEntry()
this.showAllEntries() this.showAllEntries()
//this.modelFilter.value = '' //this.modelFilter.value = ''
this.modelFilter.select() // preselect the entire string so user can just start typing. this.modelFilter.select() // preselect the entire string so user can just start typing.
this.modelFilter.focus() this.modelFilter.focus()
this.modelFilter.style.cursor = 'auto' this.modelFilter.style.cursor = "auto"
} }
hideModelList() { hideModelList() {
this.modelList.style.display = 'none' this.modelList.style.display = "none"
this.modelFilter.value = this.currentSelection.value this.modelFilter.value = this.currentSelection.value
this.modelFilter.style.cursor = '' this.modelFilter.style.cursor = ""
} }
toggleModelList(e) { toggleModelList(e) {
e.preventDefault() e.preventDefault()
if (!this.modelFilter.disabled) { if (!this.modelFilter.disabled) {
if (this.modelList.style.display != 'block') { if (this.modelList.style.display != "block") {
this.showModelList() this.showModelList()
} } else {
else
{
this.hideModelList() this.hideModelList()
this.modelFilter.select() this.modelFilter.select()
} }
@ -332,13 +335,13 @@ class ModelDropdown
selectEntry(path) { selectEntry(path) {
if (path !== undefined) { if (path !== undefined) {
const entries = this.modelElements; const entries = this.modelElements
for (const elem of entries) { for (const elem of entries) {
if (elem.dataset.path == path) { if (elem.dataset.path == path) {
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path) this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
this.highlightedModelEntry = elem this.highlightedModelEntry = elem
elem.scrollIntoView({block: 'nearest'}) elem.scrollIntoView({ block: "nearest" })
break break
} }
} }
@ -347,14 +350,12 @@ class ModelDropdown
if (this.currentSelection.elem !== undefined) { if (this.currentSelection.elem !== undefined) {
// select the previous element // select the previous element
if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != this.currentSelection.elem) { if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != this.currentSelection.elem) {
this.highlightedModelEntry.classList.remove('selected') this.highlightedModelEntry.classList.remove("selected")
} }
this.currentSelection.elem.classList.add('selected') this.currentSelection.elem.classList.add("selected")
this.highlightedModelEntry = this.currentSelection.elem this.highlightedModelEntry = this.currentSelection.elem
this.currentSelection.elem.scrollIntoView({block: 'nearest'}) this.currentSelection.elem.scrollIntoView({ block: "nearest" })
} } else {
else
{
this.selectFirstFile() this.selectFirstFile()
} }
} }
@ -362,28 +363,28 @@ class ModelDropdown
highlightModelAtPosition(e) { highlightModelAtPosition(e) {
let elem = document.elementFromPoint(e.clientX, e.clientY) let elem = document.elementFromPoint(e.clientX, e.clientY)
if (elem.classList.contains('model-file')) { if (elem.classList.contains("model-file")) {
this.highlightModel(elem) this.highlightModel(elem)
} }
} }
highlightModel(elem) { highlightModel(elem) {
if (elem.classList.contains('model-file')) { if (elem.classList.contains("model-file")) {
if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != elem) { if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != elem) {
this.highlightedModelEntry.classList.remove('selected') this.highlightedModelEntry.classList.remove("selected")
} }
elem.classList.add('selected') elem.classList.add("selected")
this.highlightedModelEntry = elem this.highlightedModelEntry = elem
} }
} }
showAllEntries() { showAllEntries() {
this.modelList.querySelectorAll('li').forEach(function(li) { this.modelList.querySelectorAll("li").forEach(function(li) {
if (li.id !== 'model-no-result') { if (li.id !== "model-no-result") {
li.style.display = 'list-item' li.style.display = "list-item"
} }
}) })
this.modelNoResult.style.display = 'none' this.modelNoResult.style.display = "none"
} }
filterList(e) { filterList(e) {
@ -391,37 +392,35 @@ class ModelDropdown
let found = false let found = false
let showAllChildren = false let showAllChildren = false
this.modelList.querySelectorAll('li').forEach(function(li) { this.modelList.querySelectorAll("li").forEach(function(li) {
if (li.classList.contains('model-folder')) { if (li.classList.contains("model-folder")) {
showAllChildren = false showAllChildren = false
} }
if (filter == '') { if (filter == "") {
li.style.display = 'list-item' li.style.display = "list-item"
found = true found = true
} else if (showAllChildren || li.textContent.toLowerCase().match(filter)) { } else if (showAllChildren || li.textContent.toLowerCase().match(filter)) {
li.style.display = 'list-item' li.style.display = "list-item"
if (li.classList.contains('model-folder') && li.firstChild.textContent.toLowerCase().match(filter)) { if (li.classList.contains("model-folder") && li.firstChild.textContent.toLowerCase().match(filter)) {
showAllChildren = true showAllChildren = true
} }
found = true found = true
} else { } else {
li.style.display = 'none' li.style.display = "none"
} }
}) })
if (found) { if (found) {
this.modelResult.style.display = 'list-item' this.modelResult.style.display = "list-item"
this.modelNoResult.style.display = 'none' this.modelNoResult.style.display = "none"
const elem = this.getNextVisibleSibling(this.modelList.querySelector('.model-file')) const elem = this.getNextVisibleSibling(this.modelList.querySelector(".model-file"))
this.highlightModel(elem) this.highlightModel(elem)
elem.scrollIntoView({block: 'nearest'}) elem.scrollIntoView({ block: "nearest" })
} else {
this.modelResult.style.display = "none"
this.modelNoResult.style.display = "list-item"
} }
else this.modelList.style.display = "block"
{
this.modelResult.style.display = 'none'
this.modelNoResult.style.display = 'list-item'
}
this.modelList.style.display = 'block'
} }
/* MODEL LOADER */ /* MODEL LOADER */
@ -458,13 +457,13 @@ class ModelDropdown
* @param {Array<string>} models * @param {Array<string>} models
*/ */
sortStringArray(models) { sortStringArray(models) {
models.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })) models.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
} }
populateModels() { populateModels() {
this.activeModel = this.modelFilter.dataset.path this.activeModel = this.modelFilter.dataset.path
this.currentSelection = { elem: undefined, value: '', path: ''} this.currentSelection = { elem: undefined, value: "", path: "" }
this.highlightedModelEntry = undefined this.highlightedModelEntry = undefined
this.flatModelList = [] this.flatModelList = []
@ -478,39 +477,39 @@ class ModelDropdown
createDropdown() { createDropdown() {
// create dropdown entries // create dropdown entries
let rootModelList = this.createRootModelList(this.inputModels) let rootModelList = this.createRootModelList(this.inputModels)
this.modelFilter.insertAdjacentElement('afterend', rootModelList) this.modelFilter.insertAdjacentElement("afterend", rootModelList)
this.modelFilter.insertAdjacentElement( this.modelFilter.insertAdjacentElement(
'afterend', "afterend",
createElement( createElement("i", { id: `${this.modelFilter.id}-model-filter-arrow` }, [
'i', "model-selector-arrow",
{ id: `${this.modelFilter.id}-model-filter-arrow` }, "fa-solid",
['model-selector-arrow', 'fa-solid', 'fa-angle-down'], "fa-angle-down"
), ])
) )
this.modelFilter.classList.add('model-selector') this.modelFilter.classList.add("model-selector")
this.modelFilterArrow = document.querySelector(`#${this.modelFilter.id}-model-filter-arrow`) this.modelFilterArrow = document.querySelector(`#${this.modelFilter.id}-model-filter-arrow`)
if (this.modelFilterArrow) { if (this.modelFilterArrow) {
this.modelFilterArrow.style.color = this.modelFilter.disabled ? 'dimgray' : '' this.modelFilterArrow.style.color = this.modelFilter.disabled ? "dimgray" : ""
} }
this.modelList = document.querySelector(`#${this.modelFilter.id}-model-list`) this.modelList = document.querySelector(`#${this.modelFilter.id}-model-list`)
this.modelResult = document.querySelector(`#${this.modelFilter.id}-model-result`) this.modelResult = document.querySelector(`#${this.modelFilter.id}-model-result`)
this.modelNoResult = document.querySelector(`#${this.modelFilter.id}-model-no-result`) this.modelNoResult = document.querySelector(`#${this.modelFilter.id}-model-no-result`)
if (this.modelFilterInitialized !== true) { if (this.modelFilterInitialized !== true) {
this.modelFilter.addEventListener('input', this.bind(this.filterList, this)) this.modelFilter.addEventListener("input", this.bind(this.filterList, this))
this.modelFilter.addEventListener('focus', this.bind(this.modelListFocus, this)) this.modelFilter.addEventListener("focus", this.bind(this.modelListFocus, this))
this.modelFilter.addEventListener('blur', this.bind(this.hideModelList, this)) this.modelFilter.addEventListener("blur", this.bind(this.hideModelList, this))
this.modelFilter.addEventListener('click', this.bind(this.showModelList, this)) this.modelFilter.addEventListener("click", this.bind(this.showModelList, this))
this.modelFilter.addEventListener('keydown', this.bind(this.processKey, this)) this.modelFilter.addEventListener("keydown", this.bind(this.processKey, this))
this.modelFilterInitialized = true this.modelFilterInitialized = true
} }
this.modelFilterArrow.addEventListener('mousedown', this.bind(this.toggleModelList, this)) this.modelFilterArrow.addEventListener("mousedown", this.bind(this.toggleModelList, this))
this.modelList.addEventListener('mousemove', this.bind(this.highlightModelAtPosition, this)) this.modelList.addEventListener("mousemove", this.bind(this.highlightModelAtPosition, this))
this.modelList.addEventListener('mousedown', this.bind(this.processClick, this)) this.modelList.addEventListener("mousedown", this.bind(this.processClick, this))
let mf = this.modelFilter let mf = this.modelFilter
this.modelFilter.addEventListener('focus', function() { this.modelFilter.addEventListener("focus", function() {
let modelFilterStyle = window.getComputedStyle(mf) let modelFilterStyle = window.getComputedStyle(mf)
rootModelList.style.minWidth = modelFilterStyle.width rootModelList.style.minWidth = modelFilterStyle.width
}) })
@ -525,69 +524,57 @@ class ModelDropdown
* @returns {HTMLElement} * @returns {HTMLElement}
*/ */
createModelNodeList(folderName, modelTree, isRootFolder) { createModelNodeList(folderName, modelTree, isRootFolder) {
const listElement = createElement('ul') const listElement = createElement("ul")
const foldersMap = new Map() const foldersMap = new Map()
const modelsMap = new Map() const modelsMap = new Map()
modelTree.forEach(model => { modelTree.forEach((model) => {
if (Array.isArray(model)) { if (Array.isArray(model)) {
const [childFolderName, childModels] = model const [childFolderName, childModels] = model
foldersMap.set( foldersMap.set(
childFolderName, childFolderName,
this.createModelNodeList( this.createModelNodeList(`${folderName || ""}/${childFolderName}`, childModels, false)
`${folderName || ''}/${childFolderName}`,
childModels,
false,
),
) )
} else { } else {
const classes = ['model-file'] const classes = ["model-file"]
if (isRootFolder) { if (isRootFolder) {
classes.push('in-root-folder') classes.push("in-root-folder")
} }
// Remove the leading slash from the model path // Remove the leading slash from the model path
const fullPath = folderName ? `${folderName.substring(1)}/${model}` : model const fullPath = folderName ? `${folderName.substring(1)}/${model}` : model
modelsMap.set( modelsMap.set(
model, model,
createElement( createElement("li", { "data-path": fullPath }, classes, [
'li', createElement("i", undefined, ["fa-regular", "fa-file", "icon"]),
{ 'data-path': fullPath }, model
classes, ])
[
createElement('i', undefined, ['fa-regular', 'fa-file', 'icon']),
model,
],
),
) )
} }
}) })
const childFolderNames = Array.from(foldersMap.keys()) const childFolderNames = Array.from(foldersMap.keys())
this.sortStringArray(childFolderNames) this.sortStringArray(childFolderNames)
const folderElements = childFolderNames.map(name => foldersMap.get(name)) const folderElements = childFolderNames.map((name) => foldersMap.get(name))
const modelNames = Array.from(modelsMap.keys()) const modelNames = Array.from(modelsMap.keys())
this.sortStringArray(modelNames) this.sortStringArray(modelNames)
const modelElements = modelNames.map(name => modelsMap.get(name)) const modelElements = modelNames.map((name) => modelsMap.get(name))
if (modelElements.length && folderName) { if (modelElements.length && folderName) {
listElement.appendChild( listElement.appendChild(
createElement( createElement(
'li', "li",
undefined, undefined,
['model-folder'], ["model-folder"],
[ [createElement("i", undefined, ["fa-regular", "fa-folder-open", "icon"]), folderName.substring(1)]
createElement('i', undefined, ['fa-regular', 'fa-folder-open', 'icon']),
folderName.substring(1),
],
) )
) )
} }
// const allModelElements = isRootFolder ? [...folderElements, ...modelElements] : [...modelElements, ...folderElements] // const allModelElements = isRootFolder ? [...folderElements, ...modelElements] : [...modelElements, ...folderElements]
const allModelElements = [...modelElements, ...folderElements] const allModelElements = [...modelElements, ...folderElements]
allModelElements.forEach(e => listElement.appendChild(e)) allModelElements.forEach((e) => listElement.appendChild(e))
return listElement return listElement
} }
@ -596,37 +583,21 @@ class ModelDropdown
* @returns {HTMLElement} * @returns {HTMLElement}
*/ */
createRootModelList(modelTree) { createRootModelList(modelTree) {
const rootList = createElement( const rootList = createElement("ul", { id: `${this.modelFilter.id}-model-list` }, ["model-list"])
'ul',
{ id: `${this.modelFilter.id}-model-list` },
['model-list'],
)
rootList.appendChild( rootList.appendChild(
createElement( createElement("li", { id: `${this.modelFilter.id}-model-no-result` }, ["model-no-result"], "No result")
'li',
{ id: `${this.modelFilter.id}-model-no-result` },
['model-no-result'],
'No result'
),
) )
if (this.noneEntry) { if (this.noneEntry) {
rootList.appendChild( rootList.appendChild(
createElement( createElement("li", { "data-path": "" }, ["model-file", "in-root-folder"], this.noneEntry)
'li',
{ 'data-path': '' },
['model-file', 'in-root-folder'],
this.noneEntry,
),
) )
} }
if (modelTree.length > 0) { if (modelTree.length > 0) {
const containerListItem = createElement( const containerListItem = createElement("li", { id: `${this.modelFilter.id}-model-result` }, [
'li', "model-result"
{ id: `${this.modelFilter.id}-model-result` }, ])
['model-result'],
)
//console.log(containerListItem) //console.log(containerListItem)
containerListItem.appendChild(this.createModelNodeList(undefined, modelTree, true)) containerListItem.appendChild(this.createModelNodeList(undefined, modelTree, true))
rootList.appendChild(containerListItem) rootList.appendChild(containerListItem)
@ -640,13 +611,16 @@ class ModelDropdown
async function getModels() { async function getModels() {
try { try {
modelsCache = await SD.getModels() modelsCache = await SD.getModels()
modelsOptions = modelsCache['options'] modelsOptions = modelsCache["options"]
if ("scan-error" in modelsCache) { if ("scan-error" in modelsCache) {
// let previewPane = document.getElementById('tab-content-wrapper') // let previewPane = document.getElementById('tab-content-wrapper')
let previewPane = document.getElementById('preview') let previewPane = document.getElementById("preview")
previewPane.style.background = "red" previewPane.style.background = "red"
previewPane.style.textAlign = "center" previewPane.style.textAlign = "center"
previewPane.innerHTML = '<H1>🔥Malware alert!🔥</H1><h2>The file <i>' + modelsCache['scan-error'] + '</i> in your <tt>models/stable-diffusion</tt> folder is probably malware infected.</h2><h2>Please delete this file from the folder before proceeding!</h2>After deleting the file, reload this page.<br><br><button onClick="window.location.reload();">Reload Page</button>' previewPane.innerHTML =
"<H1>🔥Malware alert!🔥</H1><h2>The file <i>" +
modelsCache["scan-error"] +
'</i> in your <tt>models/stable-diffusion</tt> folder is probably malware infected.</h2><h2>Please delete this file from the folder before proceeding!</h2>After deleting the file, reload this page.<br><br><button onClick="window.location.reload();">Reload Page</button>'
makeImageBtn.disabled = true makeImageBtn.disabled = true
} }
@ -667,11 +641,11 @@ async function getModels() {
*/ */
// notify ModelDropdown objects to refresh // notify ModelDropdown objects to refresh
document.dispatchEvent(new Event('refreshModels')) document.dispatchEvent(new Event("refreshModels"))
} catch (e) { } catch (e) {
console.log('get models error', e) console.log("get models error", e)
} }
} }
// reload models button // reload models button
document.querySelector('#reload-models').addEventListener('click', getModels) document.querySelector("#reload-models").addEventListener("click", getModels)

View File

@ -1,28 +1,32 @@
const themeField = document.getElementById("theme"); const themeField = document.getElementById("theme")
var DEFAULT_THEME = {}; var DEFAULT_THEME = {}
var THEMES = []; // initialized in initTheme from data in css var THEMES = [] // initialized in initTheme from data in css
function getThemeName(theme) { function getThemeName(theme) {
theme = theme.replace("theme-", ""); theme = theme.replace("theme-", "")
theme = theme.split("-").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" "); theme = theme
return theme; .split("-")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ")
return theme
} }
// init themefield // init themefield
function initTheme() { function initTheme() {
Array.from(document.styleSheets) Array.from(document.styleSheets)
.filter(sheet => sheet.href?.startsWith(window.location.origin)) .filter((sheet) => sheet.href?.startsWith(window.location.origin))
.flatMap(sheet => Array.from(sheet.cssRules)) .flatMap((sheet) => Array.from(sheet.cssRules))
.forEach(rule => { .forEach((rule) => {
var selector = rule.selectorText; var selector = rule.selectorText
if (selector && selector.startsWith(".theme-") && !selector.includes(" ")) { if (selector && selector.startsWith(".theme-") && !selector.includes(" ")) {
if (DEFAULT_THEME) { // re-add props that dont change (css needs this so they update correctly) if (DEFAULT_THEME) {
// re-add props that dont change (css needs this so they update correctly)
Array.from(DEFAULT_THEME.rule.style) Array.from(DEFAULT_THEME.rule.style)
.filter(cssVariable => !Array.from(rule.style).includes(cssVariable)) .filter((cssVariable) => !Array.from(rule.style).includes(cssVariable))
.forEach(cssVariable => { .forEach((cssVariable) => {
rule.style.setProperty(cssVariable, DEFAULT_THEME.rule.style.getPropertyValue(cssVariable)); rule.style.setProperty(cssVariable, DEFAULT_THEME.rule.style.getPropertyValue(cssVariable))
}); })
} }
var theme_key = selector.substring(1); var theme_key = selector.substring(1)
THEMES.push({ THEMES.push({
key: theme_key, key: theme_key,
name: getThemeName(theme_key), name: getThemeName(theme_key),
@ -34,49 +38,48 @@ function initTheme() {
key: "theme-default", key: "theme-default",
name: "Default", name: "Default",
rule: rule rule: rule
};
} }
}); }
})
THEMES.forEach(theme => {
var new_option = document.createElement("option");
new_option.setAttribute("value", theme.key);
new_option.innerText = theme.name;
themeField.appendChild(new_option);
});
THEMES.forEach((theme) => {
var new_option = document.createElement("option")
new_option.setAttribute("value", theme.key)
new_option.innerText = theme.name
themeField.appendChild(new_option)
})
// setup the style transitions a second after app initializes, so initial style is instant // setup the style transitions a second after app initializes, so initial style is instant
setTimeout(() => { setTimeout(() => {
var body = document.querySelector("body"); var body = document.querySelector("body")
var style = document.createElement('style'); var style = document.createElement("style")
style.innerHTML = "* { transition: background 0.5s, color 0.5s, background-color 0.5s; }"; style.innerHTML = "* { transition: background 0.5s, color 0.5s, background-color 0.5s; }"
body.appendChild(style); body.appendChild(style)
}, 1000); }, 1000)
} }
initTheme(); initTheme()
function themeFieldChanged() { function themeFieldChanged() {
var theme_key = themeField.value; var theme_key = themeField.value
var body = document.querySelector("body"); var body = document.querySelector("body")
body.classList.remove(...THEMES.map(theme => theme.key)); body.classList.remove(...THEMES.map((theme) => theme.key))
body.classList.add(theme_key); body.classList.add(theme_key)
// //
body.style = ""; body.style = ""
var theme = THEMES.find(t => t.key == theme_key); var theme = THEMES.find((t) => t.key == theme_key)
let borderColor = undefined let borderColor = undefined
if (theme) { if (theme) {
borderColor = theme.rule.style.getPropertyValue('--input-border-color').trim() borderColor = theme.rule.style.getPropertyValue("--input-border-color").trim()
if (!borderColor.startsWith('#')) { if (!borderColor.startsWith("#")) {
borderColor = theme.rule.style.getPropertyValue('--theme-color-fallback') borderColor = theme.rule.style.getPropertyValue("--theme-color-fallback")
} }
} else { } else {
borderColor = DEFAULT_THEME.rule.style.getPropertyValue('--theme-color-fallback') borderColor = DEFAULT_THEME.rule.style.getPropertyValue("--theme-color-fallback")
} }
document.querySelector('meta[name="theme-color"]').setAttribute("content", borderColor) document.querySelector('meta[name="theme-color"]').setAttribute("content", borderColor)
} }
themeField.addEventListener('change', themeFieldChanged); themeField.addEventListener("change", themeFieldChanged)

View File

@ -1,4 +1,4 @@
"use strict"; "use strict"
// https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/ // https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/
function getNextSibling(elem, selector) { function getNextSibling(elem, selector) {
@ -20,32 +20,33 @@ function getNextSibling(elem, selector) {
} }
} }
/* Panel Stuff */ /* Panel Stuff */
// true = open // true = open
let COLLAPSIBLES_INITIALIZED = false; let COLLAPSIBLES_INITIALIZED = false
const COLLAPSIBLES_KEY = "collapsibles"; const COLLAPSIBLES_KEY = "collapsibles"
const COLLAPSIBLE_PANELS = []; // filled in by createCollapsibles with all the elements matching .collapsible const COLLAPSIBLE_PANELS = [] // filled in by createCollapsibles with all the elements matching .collapsible
// on-init call this for any panels that are marked open // on-init call this for any panels that are marked open
function toggleCollapsible(element) { function toggleCollapsible(element) {
const collapsibleHeader = element.querySelector(".collapsible"); const collapsibleHeader = element.querySelector(".collapsible")
const handle = element.querySelector(".collapsible-handle"); const handle = element.querySelector(".collapsible-handle")
collapsibleHeader.classList.toggle("active") collapsibleHeader.classList.toggle("active")
let content = getNextSibling(collapsibleHeader, '.collapsible-content') let content = getNextSibling(collapsibleHeader, ".collapsible-content")
if (!collapsibleHeader.classList.contains("active")) { if (!collapsibleHeader.classList.contains("active")) {
content.style.display = "none" content.style.display = "none"
if (handle != null) { // render results don't have a handle if (handle != null) {
handle.innerHTML = '&#x2795;' // plus // render results don't have a handle
handle.innerHTML = "&#x2795;" // plus
} }
} else { } else {
content.style.display = "block" content.style.display = "block"
if (handle != null) { // render results don't have a handle if (handle != null) {
handle.innerHTML = '&#x2796;' // minus // render results don't have a handle
handle.innerHTML = "&#x2796;" // minus
} }
} }
document.dispatchEvent(new CustomEvent('collapsibleClick', { detail: collapsibleHeader })) document.dispatchEvent(new CustomEvent("collapsibleClick", { detail: collapsibleHeader }))
if (COLLAPSIBLES_INITIALIZED && COLLAPSIBLE_PANELS.includes(element)) { if (COLLAPSIBLES_INITIALIZED && COLLAPSIBLE_PANELS.includes(element)) {
saveCollapsibles() saveCollapsibles()
@ -54,7 +55,7 @@ function toggleCollapsible(element) {
function saveCollapsibles() { function saveCollapsibles() {
let values = {} let values = {}
COLLAPSIBLE_PANELS.forEach(element => { COLLAPSIBLE_PANELS.forEach((element) => {
let value = element.querySelector(".collapsible").className.indexOf("active") !== -1 let value = element.querySelector(".collapsible").className.indexOf("active") !== -1
values[element.id] = value values[element.id] = value
}) })
@ -72,31 +73,31 @@ function createCollapsibles(node) {
if (save && c.parentElement.id) { if (save && c.parentElement.id) {
COLLAPSIBLE_PANELS.push(c.parentElement) COLLAPSIBLE_PANELS.push(c.parentElement)
} }
let handle = document.createElement('span') let handle = document.createElement("span")
handle.className = 'collapsible-handle' handle.className = "collapsible-handle"
if (c.classList.contains("active")) { if (c.classList.contains("active")) {
handle.innerHTML = '&#x2796;' // minus handle.innerHTML = "&#x2796;" // minus
} else { } else {
handle.innerHTML = '&#x2795;' // plus handle.innerHTML = "&#x2795;" // plus
} }
c.insertBefore(handle, c.firstChild) c.insertBefore(handle, c.firstChild)
c.addEventListener('click', function() { c.addEventListener("click", function() {
toggleCollapsible(c.parentElement) toggleCollapsible(c.parentElement)
}) })
}) })
if (save) { if (save) {
let saved = localStorage.getItem(COLLAPSIBLES_KEY) let saved = localStorage.getItem(COLLAPSIBLES_KEY)
if (!saved) { if (!saved) {
saved = tryLoadOldCollapsibles(); saved = tryLoadOldCollapsibles()
} }
if (!saved) { if (!saved) {
saveCollapsibles() saveCollapsibles()
saved = localStorage.getItem(COLLAPSIBLES_KEY) saved = localStorage.getItem(COLLAPSIBLES_KEY)
} }
let values = JSON.parse(saved) let values = JSON.parse(saved)
COLLAPSIBLE_PANELS.forEach(element => { COLLAPSIBLE_PANELS.forEach((element) => {
let value = element.querySelector(".collapsible").className.indexOf("active") !== -1 let value = element.querySelector(".collapsible").className.indexOf("active") !== -1
if (values[element.id] != value) { if (values[element.id] != value) {
toggleCollapsible(element) toggleCollapsible(element)
@ -108,24 +109,24 @@ function createCollapsibles(node) {
function tryLoadOldCollapsibles() { function tryLoadOldCollapsibles() {
const old_map = { const old_map = {
"advancedPanelOpen": "editor-settings", advancedPanelOpen: "editor-settings",
"modifiersPanelOpen": "editor-modifiers", modifiersPanelOpen: "editor-modifiers",
"negativePromptPanelOpen": "editor-inputs-prompt" negativePromptPanelOpen: "editor-inputs-prompt"
}; }
if (localStorage.getItem(Object.keys(old_map)[0])) { if (localStorage.getItem(Object.keys(old_map)[0])) {
let result = {}; let result = {}
Object.keys(old_map).forEach(key => { Object.keys(old_map).forEach((key) => {
const value = localStorage.getItem(key); const value = localStorage.getItem(key)
if (value !== null) { if (value !== null) {
result[old_map[key]] = (value == true || value == "true") result[old_map[key]] = value == true || value == "true"
localStorage.removeItem(key) localStorage.removeItem(key)
} }
}); })
result = JSON.stringify(result) result = JSON.stringify(result)
localStorage.setItem(COLLAPSIBLES_KEY, result) localStorage.setItem(COLLAPSIBLES_KEY, result)
return result return result
} }
return null; return null
} }
function permute(arr) { function permute(arr) {
@ -134,10 +135,12 @@ function permute(arr) {
let n_permutations = Math.pow(2, n) let n_permutations = Math.pow(2, n)
for (let i = 0; i < n_permutations; i++) { for (let i = 0; i < n_permutations; i++) {
let perm = [] let perm = []
let mask = Number(i).toString(2).padStart(n, '0') let mask = Number(i)
.toString(2)
.padStart(n, "0")
for (let idx = 0; idx < mask.length; idx++) { for (let idx = 0; idx < mask.length; idx++) {
if (mask[idx] === '1' && arr[idx].trim() !== '') { if (mask[idx] === "1" && arr[idx].trim() !== "") {
perm.push(arr[idx]) perm.push(arr[idx])
} }
} }
@ -153,22 +156,22 @@ function permute(arr) {
// https://stackoverflow.com/a/8212878 // https://stackoverflow.com/a/8212878
function millisecondsToStr(milliseconds) { function millisecondsToStr(milliseconds) {
function numberEnding(number) { function numberEnding(number) {
return (number > 1) ? 's' : '' return number > 1 ? "s" : ""
} }
let temp = Math.floor(milliseconds / 1000) let temp = Math.floor(milliseconds / 1000)
let hours = Math.floor((temp %= 86400) / 3600) let hours = Math.floor((temp %= 86400) / 3600)
let s = '' let s = ""
if (hours) { if (hours) {
s += hours + ' hour' + numberEnding(hours) + ' ' s += hours + " hour" + numberEnding(hours) + " "
} }
let minutes = Math.floor((temp %= 3600) / 60) let minutes = Math.floor((temp %= 3600) / 60)
if (minutes) { if (minutes) {
s += minutes + ' minute' + numberEnding(minutes) + ' ' s += minutes + " minute" + numberEnding(minutes) + " "
} }
let seconds = temp % 60 let seconds = temp % 60
if (!hours && minutes < 4 && seconds) { if (!hours && minutes < 4 && seconds) {
s += seconds + ' second' + numberEnding(seconds) s += seconds + " second" + numberEnding(seconds)
} }
return s return s
@ -176,101 +179,82 @@ function millisecondsToStr(milliseconds) {
// https://rosettacode.org/wiki/Brace_expansion#JavaScript // https://rosettacode.org/wiki/Brace_expansion#JavaScript
function BraceExpander() { function BraceExpander() {
'use strict' "use strict"
// Index of any closing brace matching the opening // Index of any closing brace matching the opening
// brace at iPosn, // brace at iPosn,
// with the indices of any immediately-enclosed commas. // with the indices of any immediately-enclosed commas.
function bracePair(tkns, iPosn, iNest, lstCommas) { function bracePair(tkns, iPosn, iNest, lstCommas) {
if (iPosn >= tkns.length || iPosn < 0) return null; if (iPosn >= tkns.length || iPosn < 0) return null
let t = tkns[iPosn], let t = tkns[iPosn],
n = (t === '{') ? ( n = t === "{" ? iNest + 1 : t === "}" ? iNest - 1 : iNest,
iNest + 1 lst = t === "," && iNest === 1 ? lstCommas.concat(iPosn) : lstCommas
) : (t === '}' ? (
iNest - 1
) : iNest),
lst = (t === ',' && iNest === 1) ? (
lstCommas.concat(iPosn)
) : lstCommas;
return n ? bracePair(tkns, iPosn + 1, n, lst) : { return n
? bracePair(tkns, iPosn + 1, n, lst)
: {
close: iPosn, close: iPosn,
commas: lst commas: lst
}; }
} }
// Parse of a SYNTAGM subtree // Parse of a SYNTAGM subtree
function andTree(dctSofar, tkns) { function andTree(dctSofar, tkns) {
if (!tkns.length) return [dctSofar, []]; if (!tkns.length) return [dctSofar, []]
let dctParse = dctSofar ? dctSofar : { let dctParse = dctSofar
? dctSofar
: {
fn: and, fn: and,
args: [] args: []
}, },
head = tkns[0], head = tkns[0],
tail = head ? tkns.slice(1) : [], tail = head ? tkns.slice(1) : [],
dctBrace = head === "{" ? bracePair(tkns, 0, 0, []) : null,
lstOR = dctBrace && dctBrace.close && dctBrace.commas.length ? splitAt(dctBrace.close + 1, tkns) : null
dctBrace = head === '{' ? bracePair( return andTree(
tkns, 0, 0, [] {
) : null,
lstOR = dctBrace && (
dctBrace.close
) && dctBrace.commas.length ? (
splitAt(dctBrace.close + 1, tkns)
) : null;
return andTree({
fn: and, fn: and,
args: dctParse.args.concat( args: dctParse.args.concat(lstOR ? orTree(dctParse, lstOR[0], dctBrace.commas) : head)
lstOR ? ( },
orTree(dctParse, lstOR[0], dctBrace.commas) lstOR ? lstOR[1] : tail
) : head
) )
}, lstOR ? (
lstOR[1]
) : tail);
} }
// Parse of a PARADIGM subtree // Parse of a PARADIGM subtree
function orTree(dctSofar, tkns, lstCommas) { function orTree(dctSofar, tkns, lstCommas) {
if (!tkns.length) return [dctSofar, []]; if (!tkns.length) return [dctSofar, []]
let iLast = lstCommas.length; let iLast = lstCommas.length
return { return {
fn: or, fn: or,
args: splitsAt( args: splitsAt(lstCommas, tkns)
lstCommas, tkns .map(function(x, i) {
).map(function (x, i) { let ts = x.slice(1, i === iLast ? -1 : void 0)
let ts = x.slice(
1, i === iLast ? (
-1
) : void 0
);
return ts.length ? ts : ['']; return ts.length ? ts : [""]
}).map(function (ts) {
return ts.length > 1 ? (
andTree(null, ts)[0]
) : ts[0];
}) })
}; .map(function(ts) {
return ts.length > 1 ? andTree(null, ts)[0] : ts[0]
})
}
} }
// List of unescaped braces and commas, and remaining strings // List of unescaped braces and commas, and remaining strings
function tokens(str) { function tokens(str) {
// Filter function excludes empty splitting artefacts // Filter function excludes empty splitting artefacts
let toS = function(x) { let toS = function(x) {
return x.toString(); return x.toString()
}; }
return str.split(/(\\\\)/).filter(toS).reduce(function (a, s) { return str
return a.concat(s.charAt(0) === '\\' ? s : s.split( .split(/(\\\\)/)
/(\\*[{,}])/ .filter(toS)
).filter(toS)); .reduce(function(a, s) {
}, []); return a.concat(s.charAt(0) === "\\" ? s : s.split(/(\\*[{,}])/).filter(toS))
}, [])
} }
// PARSE TREE OPERATOR (1 of 2) // PARSE TREE OPERATOR (1 of 2)
@ -278,76 +262,75 @@ function BraceExpander() {
function and(args) { function and(args) {
let lng = args.length, let lng = args.length,
head = lng ? args[0] : null, head = lng ? args[0] : null,
lstHead = "string" === typeof head ? ( lstHead = "string" === typeof head ? [head] : head
[head]
) : head;
return lng ? ( return lng
1 < lng ? lstHead.reduce(function (a, h) { ? 1 < lng
? lstHead.reduce(function(a, h) {
return a.concat( return a.concat(
and(args.slice(1)).map(function(t) { and(args.slice(1)).map(function(t) {
return h + t; return h + t
}) })
); )
}, []) : lstHead }, [])
) : []; : lstHead
: []
} }
// PARSE TREE OPERATOR (2 of 2) // PARSE TREE OPERATOR (2 of 2)
// Each option flattened // Each option flattened
function or(args) { function or(args) {
return args.reduce(function(a, b) { return args.reduce(function(a, b) {
return a.concat(b); return a.concat(b)
}, []); }, [])
} }
// One list split into two (first sublist length n) // One list split into two (first sublist length n)
function splitAt(n, lst) { function splitAt(n, lst) {
return n < lst.length + 1 ? [ return n < lst.length + 1 ? [lst.slice(0, n), lst.slice(n)] : [lst, []]
lst.slice(0, n), lst.slice(n)
] : [lst, []];
} }
// One list split into several (sublist lengths [n]) // One list split into several (sublist lengths [n])
function splitsAt(lstN, lst) { function splitsAt(lstN, lst) {
return lstN.reduceRight(function (a, x) { return lstN.reduceRight(
return splitAt(x, a[0]).concat(a.slice(1)); function(a, x) {
}, [lst]); return splitAt(x, a[0]).concat(a.slice(1))
},
[lst]
)
} }
// Value of the parse tree // Value of the parse tree
function evaluated(e) { function evaluated(e) {
return typeof e === 'string' ? e : return typeof e === "string" ? e : e.fn(e.args.map(evaluated))
e.fn(e.args.map(evaluated));
} }
// JSON prettyprint (for parse tree, token list etc) // JSON prettyprint (for parse tree, token list etc)
function pp(e) { function pp(e) {
return JSON.stringify(e, function (k, v) { return JSON.stringify(
return typeof v === 'function' ? ( e,
'[function ' + v.name + ']' function(k, v) {
) : v; return typeof v === "function" ? "[function " + v.name + "]" : v
}, 2) },
2
)
} }
// ----------------------- MAIN ------------------------ // ----------------------- MAIN ------------------------
// s -> [s] // s -> [s]
this.expand = function(s) { this.expand = function(s) {
// BRACE EXPRESSION PARSED // BRACE EXPRESSION PARSED
let dctParse = andTree(null, tokens(s))[0]; let dctParse = andTree(null, tokens(s))[0]
// ABSTRACT SYNTAX TREE LOGGED // ABSTRACT SYNTAX TREE LOGGED
// console.log(pp(dctParse)); // console.log(pp(dctParse));
// AST EVALUATED TO LIST OF STRINGS // AST EVALUATED TO LIST OF STRINGS
return evaluated(dctParse); return evaluated(dctParse)
} }
} }
/** Pause the execution of an async function until timer elapse. /** Pause the execution of an async function until timer elapse.
* @Returns a promise that will resolve after the specified timeout. * @Returns a promise that will resolve after the specified timeout.
*/ */
@ -360,8 +343,8 @@ function asyncDelay(timeout) {
function PromiseSource() { function PromiseSource() {
const srcPromise = new Promise((resolve, reject) => { const srcPromise = new Promise((resolve, reject) => {
Object.defineProperties(this, { Object.defineProperties(this, {
resolve: { value: resolve, writable: false } resolve: { value: resolve, writable: false },
, reject: { value: reject, writable: false } reject: { value: reject, writable: false }
}) })
}) })
Object.defineProperties(this, { Object.defineProperties(this, {
@ -399,7 +382,7 @@ function debounce (func, wait, immediate) {
} }
return function(...args) { return function(...args) {
const callNow = Boolean(immediate && !timeout) const callNow = Boolean(immediate && !timeout)
const context = this; const context = this
if (timeout) { if (timeout) {
clearTimeout(timeout) clearTimeout(timeout)
} }
@ -418,14 +401,14 @@ function debounce (func, wait, immediate) {
} }
function preventNonNumericalInput(e) { function preventNonNumericalInput(e) {
e = e || window.event; e = e || window.event
let charCode = (typeof e.which == "undefined") ? e.keyCode : e.which; let charCode = typeof e.which == "undefined" ? e.keyCode : e.which
let charStr = String.fromCharCode(charCode); let charStr = String.fromCharCode(charCode)
let re = e.target.getAttribute('pattern') || '^[0-9]+$' let re = e.target.getAttribute("pattern") || "^[0-9]+$"
re = new RegExp(re) re = new RegExp(re)
if (!charStr.match(re)) { if (!charStr.match(re)) {
e.preventDefault(); e.preventDefault()
} }
} }
@ -434,15 +417,15 @@ function preventNonNumericalInput(e) {
* @Notes Allows unit testing and use of the engine outside of a browser. * @Notes Allows unit testing and use of the engine outside of a browser.
*/ */
function getGlobal() { function getGlobal() {
if (typeof globalThis === 'object') { if (typeof globalThis === "object") {
return globalThis return globalThis
} else if (typeof global === 'object') { } else if (typeof global === "object") {
return global return global
} else if (typeof self === 'object') { } else if (typeof self === "object") {
return self return self
} }
try { try {
return Function('return this')() return Function("return this")()
} catch { } catch {
// If the Function constructor fails, we're in a browser with eval disabled by CSP headers. // If the Function constructor fails, we're in a browser with eval disabled by CSP headers.
return window return window
@ -453,18 +436,18 @@ function getGlobal() {
* @Returns true if x is an Array or a TypedArray, false otherwise. * @Returns true if x is an Array or a TypedArray, false otherwise.
*/ */
function isArrayOrTypedArray(x) { function isArrayOrTypedArray(x) {
return Boolean(typeof x === 'object' && (Array.isArray(x) || (ArrayBuffer.isView(x) && !(x instanceof DataView)))) return Boolean(typeof x === "object" && (Array.isArray(x) || (ArrayBuffer.isView(x) && !(x instanceof DataView))))
} }
function makeQuerablePromise(promise) { function makeQuerablePromise(promise) {
if (typeof promise !== 'object') { if (typeof promise !== "object") {
throw new Error('promise is not an object.') throw new Error("promise is not an object.")
} }
if (!(promise instanceof Promise)) { if (!(promise instanceof Promise)) {
throw new Error('Argument is not a promise.') throw new Error("Argument is not a promise.")
} }
// Don't modify a promise that's been already modified. // Don't modify a promise that's been already modified.
if ('isResolved' in promise || 'isRejected' in promise || 'isPending' in promise) { if ("isResolved" in promise || "isRejected" in promise || "isPending" in promise) {
return promise return promise
} }
let isPending = true let isPending = true
@ -478,8 +461,8 @@ function makeQuerablePromise(promise) {
isPending = false isPending = false
resolvedValue = val resolvedValue = val
return val return val
} },
, function(reason) { function(reason) {
rejectReason = reason rejectReason = reason
isRejected = true isRejected = true
isPending = false isPending = false
@ -487,19 +470,19 @@ function makeQuerablePromise(promise) {
} }
) )
Object.defineProperties(qurPro, { Object.defineProperties(qurPro, {
'isResolved': { isResolved: {
get: () => isResolved get: () => isResolved
} },
, 'resolvedValue': { resolvedValue: {
get: () => resolvedValue get: () => resolvedValue
} },
, 'isPending': { isPending: {
get: () => isPending get: () => isPending
} },
, 'isRejected': { isRejected: {
get: () => isRejected get: () => isRejected
} },
, 'rejectReason': { rejectReason: {
get: () => rejectReason get: () => rejectReason
} }
}) })
@ -508,25 +491,25 @@ function makeQuerablePromise(promise) {
/* inserts custom html to allow prettifying of inputs */ /* inserts custom html to allow prettifying of inputs */
function prettifyInputs(root_element) { function prettifyInputs(root_element) {
root_element.querySelectorAll(`input[type="checkbox"]`).forEach(element => { root_element.querySelectorAll(`input[type="checkbox"]`).forEach((element) => {
if (element.style.display === "none") { if (element.style.display === "none") {
return return
} }
var parent = element.parentNode; var parent = element.parentNode
if (!parent.classList.contains("input-toggle")) { if (!parent.classList.contains("input-toggle")) {
var wrapper = document.createElement("div"); var wrapper = document.createElement("div")
wrapper.classList.add("input-toggle"); wrapper.classList.add("input-toggle")
parent.replaceChild(wrapper, element); parent.replaceChild(wrapper, element)
wrapper.appendChild(element); wrapper.appendChild(element)
var label = document.createElement("label"); var label = document.createElement("label")
label.htmlFor = element.id; label.htmlFor = element.id
wrapper.appendChild(label); wrapper.appendChild(label)
} }
}) })
} }
class GenericEventSource { class GenericEventSource {
#events = {}; #events = {}
#types = [] #types = []
constructor(...eventsTypes) { constructor(...eventsTypes) {
if (Array.isArray(eventsTypes) && eventsTypes.length === 1 && Array.isArray(eventsTypes[0])) { if (Array.isArray(eventsTypes) && eventsTypes.length === 1 && Array.isArray(eventsTypes[0])) {
@ -541,7 +524,7 @@ class GenericEventSource {
*/ */
addEventListener(name, handler) { addEventListener(name, handler) {
if (!this.#types.includes(name)) { if (!this.#types.includes(name)) {
throw new Error('Invalid event name.') throw new Error("Invalid event name.")
} }
if (this.#events.hasOwnProperty(name)) { if (this.#events.hasOwnProperty(name)) {
this.#events[name].push(handler) this.#events[name].push(handler)
@ -574,13 +557,15 @@ class GenericEventSource {
if (evs.length <= 0) { if (evs.length <= 0) {
return Promise.resolve() return Promise.resolve()
} }
return Promise.allSettled(evs.map((callback) => { return Promise.allSettled(
evs.map((callback) => {
try { try {
return Promise.resolve(callback.apply(SD, args)) return Promise.resolve(callback.apply(SD, args))
} catch (ex) { } catch (ex) {
return Promise.reject(ex) return Promise.reject(ex)
} }
})) })
)
} }
} }
@ -598,33 +583,31 @@ class ServiceContainer {
} }
register(params) { register(params) {
if (ServiceContainer.isConstructor(params)) { if (ServiceContainer.isConstructor(params)) {
if (typeof params.name !== 'string') { if (typeof params.name !== "string") {
throw new Error('params.name is not a string.') throw new Error("params.name is not a string.")
} }
params = { name: params.name, definition: params } params = { name: params.name, definition: params }
} }
if (typeof params !== 'object') { if (typeof params !== "object") {
throw new Error('params is not an object.') throw new Error("params is not an object.")
} }
[ 'name', ;["name", "definition"].forEach((key) => {
'definition',
].forEach((key) => {
if (!(key in params)) { if (!(key in params)) {
console.error('Invalid service %o registration.', params) console.error("Invalid service %o registration.", params)
throw new Error(`params.${key} is not defined.`) throw new Error(`params.${key} is not defined.`)
} }
}) })
const opts = { definition: params.definition } const opts = { definition: params.definition }
if ('dependencies' in params) { if ("dependencies" in params) {
if (Array.isArray(params.dependencies)) { if (Array.isArray(params.dependencies)) {
params.dependencies.forEach((dep) => { params.dependencies.forEach((dep) => {
if (typeof dep !== 'string') { if (typeof dep !== "string") {
throw new Error('dependency name is not a string.') throw new Error("dependency name is not a string.")
} }
}) })
opts.dependencies = params.dependencies opts.dependencies = params.dependencies
} else { } else {
throw new Error('params.dependencies is not an array.') throw new Error("params.dependencies is not an array.")
} }
} }
if (params.singleton) { if (params.singleton) {
@ -671,10 +654,14 @@ class ServiceContainer {
} }
static isClass(definition) { static isClass(definition) {
return typeof definition === 'function' && Boolean(definition.prototype) && definition.prototype.constructor === definition return (
typeof definition === "function" &&
Boolean(definition.prototype) &&
definition.prototype.constructor === definition
)
} }
static isConstructor(definition) { static isConstructor(definition) {
return typeof definition === 'function' return typeof definition === "function"
} }
} }
@ -693,14 +680,14 @@ function createElement(tagName, attributes, classes, textOrElements) {
if (value !== undefined && value !== null) { if (value !== undefined && value !== null) {
element.setAttribute(key, value) element.setAttribute(key, value)
} }
}); })
} }
if (classes) { if (classes) {
(Array.isArray(classes) ? classes : [classes]).forEach(className => element.classList.add(className)) ;(Array.isArray(classes) ? classes : [classes]).forEach((className) => element.classList.add(className))
} }
if (textOrElements) { if (textOrElements) {
const children = Array.isArray(textOrElements) ? textOrElements : [textOrElements] const children = Array.isArray(textOrElements) ? textOrElements : [textOrElements]
children.forEach(textOrElem => { children.forEach((textOrElem) => {
if (textOrElem instanceof Node) { if (textOrElem instanceof Node) {
element.appendChild(textOrElem) element.appendChild(textOrElem)
} else { } else {
@ -752,31 +739,31 @@ Array.prototype.addEventListener = function(method, callback) {
*/ */
function createTab(request) { function createTab(request) {
if (!request?.id) { if (!request?.id) {
console.error('createTab() error - id is required', Error().stack) console.error("createTab() error - id is required", Error().stack)
return return
} }
if (!request.label) { if (!request.label) {
console.error('createTab() error - label is required', Error().stack) console.error("createTab() error - label is required", Error().stack)
return return
} }
if (!request.icon) { if (!request.icon) {
console.error('createTab() error - icon is required', Error().stack) console.error("createTab() error - icon is required", Error().stack)
return return
} }
if (!request.content && !request.onOpen) { if (!request.content && !request.onOpen) {
console.error('createTab() error - content or onOpen required', Error().stack) console.error("createTab() error - content or onOpen required", Error().stack)
return return
} }
const tabsContainer = document.querySelector('.tab-container') const tabsContainer = document.querySelector(".tab-container")
if (!tabsContainer) { if (!tabsContainer) {
return return
} }
const tabsContentWrapper = document.querySelector('#tab-content-wrapper') const tabsContentWrapper = document.querySelector("#tab-content-wrapper")
if (!tabsContentWrapper) { if (!tabsContentWrapper) {
return return
} }
@ -784,41 +771,37 @@ Array.prototype.addEventListener = function(method, callback) {
// console.debug('creating tab: ', request) // console.debug('creating tab: ', request)
if (request.css) { if (request.css) {
document.querySelector('body').insertAdjacentElement( document
'beforeend', .querySelector("body")
createElement('style', { id: `tab-${request.id}-css` }, undefined, request.css), .insertAdjacentElement(
"beforeend",
createElement("style", { id: `tab-${request.id}-css` }, undefined, request.css)
) )
} }
const label = typeof request.label === 'function' ? request.label() : request.label const label = typeof request.label === "function" ? request.label() : request.label
const labelElement = label instanceof Node ? label : createElement('span', undefined, undefined, label) const labelElement = label instanceof Node ? label : createElement("span", undefined, undefined, label)
const tab = createElement( const tab = createElement(
'span', "span",
{ id: `tab-${request.id}`, 'data-times-opened': 0 }, { id: `tab-${request.id}`, "data-times-opened": 0 },
['tab'], ["tab"],
createElement( createElement("span", undefined, undefined, [
'span', createElement("i", { style: "margin-right: 0.25em" }, [
undefined, "fa-solid",
undefined, `${request.icon.startsWith("fa-") ? "" : "fa-"}${request.icon}`,
[ "icon"
createElement( ]),
'i', labelElement
{ style: 'margin-right: 0.25em' }, ])
['fa-solid', `${request.icon.startsWith('fa-') ? '' : 'fa-'}${request.icon}`, 'icon'],
),
labelElement,
],
)
) )
tabsContainer.insertAdjacentElement("beforeend", tab)
tabsContainer.insertAdjacentElement('beforeend', tab) const wrapper = createElement("div", { id: request.id }, ["tab-content-inner"], "Loading..")
const wrapper = createElement('div', { id: request.id }, ['tab-content-inner'], 'Loading..') const tabContent = createElement("div", { id: `tab-content-${request.id}` }, ["tab-content"], wrapper)
tabsContentWrapper.insertAdjacentElement("beforeend", tabContent)
const tabContent = createElement('div', { id: `tab-content-${request.id}` }, ['tab-content'], wrapper)
tabsContentWrapper.insertAdjacentElement('beforeend', tabContent)
linkTabContents(tab) linkTabContents(tab)
@ -826,7 +809,7 @@ Array.prototype.addEventListener = function(method, callback) {
if (resultFactory === undefined || resultFactory === null) { if (resultFactory === undefined || resultFactory === null) {
return return
} }
const result = typeof resultFactory === 'function' ? resultFactory() : resultFactory const result = typeof resultFactory === "function" ? resultFactory() : resultFactory
if (result instanceof Promise) { if (result instanceof Promise) {
result.then(replaceContent) result.then(replaceContent)
} else if (result instanceof Node) { } else if (result instanceof Node) {
@ -838,7 +821,7 @@ Array.prototype.addEventListener = function(method, callback) {
replaceContent(request.content) replaceContent(request.content)
tab.addEventListener('click', (e) => { tab.addEventListener("click", (e) => {
const timesOpened = +(tab.dataset.timesOpened || 0) + 1 const timesOpened = +(tab.dataset.timesOpened || 0) + 1
tab.dataset.timesOpened = timesOpened tab.dataset.timesOpened = timesOpened
@ -848,9 +831,9 @@ Array.prototype.addEventListener = function(method, callback) {
contentElement: wrapper, contentElement: wrapper,
labelElement, labelElement,
timesOpened, timesOpened,
firstOpen: timesOpened === 1, firstOpen: timesOpened === 1
}, },
e, e
) )
replaceContent(result) replaceContent(result)

View File

@ -1,4 +1,4 @@
(function () { ;(function() {
"use strict" "use strict"
let autoScroll = document.querySelector("#auto_scroll") let autoScroll = document.querySelector("#auto_scroll")
@ -6,23 +6,27 @@
// observe for changes in the preview pane // observe for changes in the preview pane
var observer = new MutationObserver(function(mutations) { var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) { mutations.forEach(function(mutation) {
if (mutation.target.className == 'img-batch') { if (mutation.target.className == "img-batch") {
Autoscroll(mutation.target) Autoscroll(mutation.target)
} }
}) })
}) })
observer.observe(document.getElementById('preview'), { observer.observe(document.getElementById("preview"), {
childList: true, childList: true,
subtree: true subtree: true
}) })
function Autoscroll(target) { function Autoscroll(target) {
if (autoScroll.checked && target !== null) { if (autoScroll.checked && target !== null) {
const img = target.querySelector('img') const img = target.querySelector("img")
img.addEventListener('load', function() { img.addEventListener(
img.closest('.imageTaskContainer').scrollIntoView() "load",
}, { once: true }) function() {
img.closest(".imageTaskContainer").scrollIntoView()
},
{ once: true }
)
} }
} }
})() })()

View File

@ -1,18 +1,19 @@
(function () { "use strict" ;(function() {
if (typeof editorModifierTagsList !== 'object') { "use strict"
console.error('editorModifierTagsList missing...') if (typeof editorModifierTagsList !== "object") {
console.error("editorModifierTagsList missing...")
return return
} }
const styleSheet = document.createElement("style"); const styleSheet = document.createElement("style")
styleSheet.textContent = ` styleSheet.textContent = `
.modifier-card-tiny.drag-sort-active { .modifier-card-tiny.drag-sort-active {
background: transparent; background: transparent;
border: 2px dashed white; border: 2px dashed white;
opacity:0.2; opacity:0.2;
} }
`; `
document.head.appendChild(styleSheet); document.head.appendChild(styleSheet)
// observe for changes in tag list // observe for changes in tag list
const observer = new MutationObserver(function(mutations) { const observer = new MutationObserver(function(mutations) {
@ -29,65 +30,87 @@
let current let current
function ModifierDragAndDrop(target) { function ModifierDragAndDrop(target) {
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay') let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay")
overlays.forEach (i => { overlays.forEach((i) => {
i.parentElement.draggable = true; i.parentElement.draggable = true
i.parentElement.ondragstart = (e) => { i.parentElement.ondragstart = (e) => {
current = i current = i
i.parentElement.getElementsByClassName('modifier-card-image-overlay')[0].innerText = '' i.parentElement.getElementsByClassName("modifier-card-image-overlay")[0].innerText = ""
i.parentElement.draggable = true i.parentElement.draggable = true
i.parentElement.classList.add('drag-sort-active') i.parentElement.classList.add("drag-sort-active")
for(let item of document.querySelector('#editor-inputs-tags-list').getElementsByClassName('modifier-card-image-overlay')) { for (let item of document
if (item.parentElement.parentElement.getElementsByClassName('modifier-card-overlay')[0] != current) { .querySelector("#editor-inputs-tags-list")
item.parentElement.parentElement.getElementsByClassName('modifier-card-image-overlay')[0].style.opacity = 0 .getElementsByClassName("modifier-card-image-overlay")) {
if(item.parentElement.getElementsByClassName('modifier-card-image').length > 0) { if (
item.parentElement.getElementsByClassName('modifier-card-image')[0].style.filter = 'none' item.parentElement.parentElement.getElementsByClassName("modifier-card-overlay")[0] != current
) {
item.parentElement.parentElement.getElementsByClassName(
"modifier-card-image-overlay"
)[0].style.opacity = 0
if (item.parentElement.getElementsByClassName("modifier-card-image").length > 0) {
item.parentElement.getElementsByClassName("modifier-card-image")[0].style.filter = "none"
} }
item.parentElement.parentElement.style.transform = 'none' item.parentElement.parentElement.style.transform = "none"
item.parentElement.parentElement.style.boxShadow = 'none' item.parentElement.parentElement.style.boxShadow = "none"
} }
item.innerText = '' item.innerText = ""
} }
} }
i.ondragenter = (e) => { i.ondragenter = (e) => {
e.preventDefault() e.preventDefault()
if (i != current) { if (i != current) {
let currentPos = 0, droppedPos = 0; let currentPos = 0,
droppedPos = 0
for (let it = 0; it < overlays.length; it++) { for (let it = 0; it < overlays.length; it++) {
if (current == overlays[it]) { currentPos = it; } if (current == overlays[it]) {
if (i == overlays[it]) { droppedPos = it; } currentPos = it
}
if (i == overlays[it]) {
droppedPos = it
}
} }
if (i.parentElement != current.parentElement) { if (i.parentElement != current.parentElement) {
let currentPos = 0, droppedPos = 0 let currentPos = 0,
droppedPos = 0
for (let it = 0; it < overlays.length; it++) { for (let it = 0; it < overlays.length; it++) {
if (current == overlays[it]) { currentPos = it } if (current == overlays[it]) {
if (i == overlays[it]) { droppedPos = it } currentPos = it
}
if (i == overlays[it]) {
droppedPos = it
}
} }
if (currentPos < droppedPos) { if (currentPos < droppedPos) {
current = i.parentElement.parentNode.insertBefore(current.parentElement, i.parentElement.nextSibling).getElementsByClassName('modifier-card-overlay')[0] current = i.parentElement.parentNode
.insertBefore(current.parentElement, i.parentElement.nextSibling)
.getElementsByClassName("modifier-card-overlay")[0]
} else { } else {
current = i.parentElement.parentNode.insertBefore(current.parentElement, i.parentElement).getElementsByClassName('modifier-card-overlay')[0] current = i.parentElement.parentNode
.insertBefore(current.parentElement, i.parentElement)
.getElementsByClassName("modifier-card-overlay")[0]
} }
// update activeTags // update activeTags
const tag = activeTags.splice(currentPos, 1) const tag = activeTags.splice(currentPos, 1)
activeTags.splice(droppedPos, 0, tag[0]) activeTags.splice(droppedPos, 0, tag[0])
document.dispatchEvent(new Event('refreshImageModifiers')) document.dispatchEvent(new Event("refreshImageModifiers"))
}
} }
} }
};
i.ondragover = (e) => { i.ondragover = (e) => {
e.preventDefault() e.preventDefault()
} }
i.parentElement.ondragend = (e) => { i.parentElement.ondragend = (e) => {
i.parentElement.classList.remove('drag-sort-active') i.parentElement.classList.remove("drag-sort-active")
for(let item of document.querySelector('#editor-inputs-tags-list').getElementsByClassName('modifier-card-image-overlay')) { for (let item of document
item.style.opacity = '' .querySelector("#editor-inputs-tags-list")
item.innerText = '-' .getElementsByClassName("modifier-card-image-overlay")) {
item.style.opacity = ""
item.innerText = "-"
} }
} }
}) })

View File

@ -1,10 +1,10 @@
(function () { ;(function() {
"use strict" "use strict"
const MAX_WEIGHT = 5 const MAX_WEIGHT = 5
if (typeof editorModifierTagsList !== 'object') { if (typeof editorModifierTagsList !== "object") {
console.error('editorModifierTagsList missing...') console.error("editorModifierTagsList missing...")
return return
} }
@ -22,14 +22,16 @@
}) })
function ModifierMouseWheel(target) { function ModifierMouseWheel(target) {
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay') let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay")
overlays.forEach (i => { overlays.forEach((i) => {
i.onwheel = (e) => { i.onwheel = (e) => {
if (e.ctrlKey == true) { if (e.ctrlKey == true) {
e.preventDefault() e.preventDefault()
const delta = Math.sign(event.deltaY) const delta = Math.sign(event.deltaY)
let s = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].innerText let s = i.parentElement
.getElementsByClassName("modifier-card-label")[0]
.getElementsByTagName("p")[0].innerText
let t let t
// find the corresponding tag // find the corresponding tag
for (let it = 0; it < overlays.length; it++) { for (let it = 0; it < overlays.length; it++) {
@ -38,43 +40,40 @@
break break
} }
} }
if (s.charAt(0) !== '(' && s.charAt(s.length - 1) !== ')' && s.trim().includes(' ')) { if (s.charAt(0) !== "(" && s.charAt(s.length - 1) !== ")" && s.trim().includes(" ")) {
s = '(' + s + ')' s = "(" + s + ")"
t = '(' + t + ')' t = "(" + t + ")"
} }
if (delta < 0) { if (delta < 0) {
// wheel scrolling up // wheel scrolling up
if (s.substring(s.length - 1) == '-') { if (s.substring(s.length - 1) == "-") {
s = s.substring(0, s.length - 1) s = s.substring(0, s.length - 1)
t = t.substring(0, t.length - 1) t = t.substring(0, t.length - 1)
} } else {
else if (s.substring(s.length - MAX_WEIGHT) !== "+".repeat(MAX_WEIGHT)) {
{ s = s + "+"
if (s.substring(s.length - MAX_WEIGHT) !== '+'.repeat(MAX_WEIGHT)) { t = t + "+"
s = s + '+'
t = t + '+'
} }
} }
} } else {
else{
// wheel scrolling down // wheel scrolling down
if (s.substring(s.length - 1) == '+') { if (s.substring(s.length - 1) == "+") {
s = s.substring(0, s.length - 1) s = s.substring(0, s.length - 1)
t = t.substring(0, t.length - 1) t = t.substring(0, t.length - 1)
} } else {
else if (s.substring(s.length - MAX_WEIGHT) !== "-".repeat(MAX_WEIGHT)) {
{ s = s + "-"
if (s.substring(s.length - MAX_WEIGHT) !== '-'.repeat(MAX_WEIGHT)) { t = t + "-"
s = s + '-'
t = t + '-'
} }
} }
} }
if (s.charAt(0) === '(' && s.charAt(s.length - 1) === ')') { if (s.charAt(0) === "(" && s.charAt(s.length - 1) === ")") {
s = s.substring(1, s.length - 1) s = s.substring(1, s.length - 1)
t = t.substring(1, t.length - 1) t = t.substring(1, t.length - 1)
} }
i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].innerText = s i.parentElement
.getElementsByClassName("modifier-card-label")[0]
.getElementsByTagName("p")[0].innerText = s
// update activeTags // update activeTags
for (let it = 0; it < overlays.length; it++) { for (let it = 0; it < overlays.length; it++) {
if (i == overlays[it]) { if (i == overlays[it]) {
@ -82,7 +81,7 @@
break break
} }
} }
document.dispatchEvent(new Event('refreshImageModifiers')) document.dispatchEvent(new Event("refreshImageModifiers"))
} }
} }
}) })

View File

@ -1,25 +1,25 @@
(function() { ;(function() {
PLUGINS['MODIFIERS_LOAD'].push({ PLUGINS["MODIFIERS_LOAD"].push({
loader: function() { loader: function() {
let customModifiers = localStorage.getItem(CUSTOM_MODIFIERS_KEY, '') let customModifiers = localStorage.getItem(CUSTOM_MODIFIERS_KEY, "")
customModifiersTextBox.value = customModifiers customModifiersTextBox.value = customModifiers
if (customModifiersGroupElement !== undefined) { if (customModifiersGroupElement !== undefined) {
customModifiersGroupElement.remove() customModifiersGroupElement.remove()
} }
if (customModifiers && customModifiers.trim() !== '') { if (customModifiers && customModifiers.trim() !== "") {
customModifiers = customModifiers.split('\n') customModifiers = customModifiers.split("\n")
customModifiers = customModifiers.filter(m => m.trim() !== '') customModifiers = customModifiers.filter((m) => m.trim() !== "")
customModifiers = customModifiers.map(function(m) { customModifiers = customModifiers.map(function(m) {
return { return {
"modifier": m modifier: m
} }
}) })
let customGroup = { let customGroup = {
'category': 'Custom Modifiers', category: "Custom Modifiers",
'modifiers': customModifiers modifiers: customModifiers
} }
customModifiersGroupElement = createModifierGroup(customGroup, true) customModifiersGroupElement = createModifierGroup(customGroup, true)

View File

@ -26,8 +26,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
after `jasmine.js` and `jasmine_html.js`, but before `boot1.js` or any project after `jasmine.js` and `jasmine_html.js`, but before `boot1.js` or any project
source files or spec files are loaded. source files or spec files are loaded.
*/ */
(function() { ;(function() {
const jasmineRequire = window.jasmineRequire || require('./jasmine.js'); const jasmineRequire = window.jasmineRequire || require("./jasmine.js")
/** /**
* ## Require &amp; Instantiate * ## Require &amp; Instantiate
@ -35,30 +35,30 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
*/ */
const jasmine = jasmineRequire.core(jasmineRequire), const jasmine = jasmineRequire.core(jasmineRequire),
global = jasmine.getGlobal(); global = jasmine.getGlobal()
global.jasmine = jasmine; global.jasmine = jasmine
/** /**
* Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
*/ */
jasmineRequire.html(jasmine); jasmineRequire.html(jasmine)
/** /**
* Create the Jasmine environment. This is used to run all specs in a project. * Create the Jasmine environment. This is used to run all specs in a project.
*/ */
const env = jasmine.getEnv(); const env = jasmine.getEnv()
/** /**
* ## The Global Interface * ## The Global Interface
* *
* Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
*/ */
const jasmineInterface = jasmineRequire.interface(jasmine, env); const jasmineInterface = jasmineRequire.interface(jasmine, env)
/** /**
* Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. * Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
*/ */
for (const property in jasmineInterface) { for (const property in jasmineInterface) {
global[property] = jasmineInterface[property]; global[property] = jasmineInterface[property]
} }
})(); })()

View File

@ -33,8 +33,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
after `boot0.js` is loaded and before this file is loaded. after `boot0.js` is loaded and before this file is loaded.
*/ */
(function() { ;(function() {
const env = jasmine.getEnv(); const env = jasmine.getEnv()
/** /**
* ## Runner Parameters * ## Runner Parameters
@ -44,29 +44,27 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
const queryString = new jasmine.QueryString({ const queryString = new jasmine.QueryString({
getWindowLocation: function() { getWindowLocation: function() {
return window.location; return window.location
} }
}); })
const filterSpecs = !!queryString.getParam('spec'); const filterSpecs = !!queryString.getParam("spec")
const config = { const config = {
stopOnSpecFailure: queryString.getParam('stopOnSpecFailure'), stopOnSpecFailure: queryString.getParam("stopOnSpecFailure"),
stopSpecOnExpectationFailure: queryString.getParam( stopSpecOnExpectationFailure: queryString.getParam("stopSpecOnExpectationFailure"),
'stopSpecOnExpectationFailure' hideDisabled: queryString.getParam("hideDisabled")
),
hideDisabled: queryString.getParam('hideDisabled')
};
const random = queryString.getParam('random');
if (random !== undefined && random !== '') {
config.random = random;
} }
const seed = queryString.getParam('seed'); const random = queryString.getParam("random")
if (random !== undefined && random !== "") {
config.random = random
}
const seed = queryString.getParam("seed")
if (seed) { if (seed) {
config.seed = seed; config.seed = seed
} }
/** /**
@ -76,57 +74,57 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
const htmlReporter = new jasmine.HtmlReporter({ const htmlReporter = new jasmine.HtmlReporter({
env: env, env: env,
navigateWithNewParam: function(key, value) { navigateWithNewParam: function(key, value) {
return queryString.navigateWithNewParam(key, value); return queryString.navigateWithNewParam(key, value)
}, },
addToExistingQueryString: function(key, value) { addToExistingQueryString: function(key, value) {
return queryString.fullStringWithNewParam(key, value); return queryString.fullStringWithNewParam(key, value)
}, },
getContainer: function() { getContainer: function() {
return document.body; return document.body
}, },
createElement: function() { createElement: function() {
return document.createElement.apply(document, arguments); return document.createElement.apply(document, arguments)
}, },
createTextNode: function() { createTextNode: function() {
return document.createTextNode.apply(document, arguments); return document.createTextNode.apply(document, arguments)
}, },
timer: new jasmine.Timer(), timer: new jasmine.Timer(),
filterSpecs: filterSpecs filterSpecs: filterSpecs
}); })
/** /**
* The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
*/ */
env.addReporter(jsApiReporter); env.addReporter(jsApiReporter)
env.addReporter(htmlReporter); env.addReporter(htmlReporter)
/** /**
* Filter which specs will be run by matching the start of the full name against the `spec` query param. * Filter which specs will be run by matching the start of the full name against the `spec` query param.
*/ */
const specFilter = new jasmine.HtmlSpecFilter({ const specFilter = new jasmine.HtmlSpecFilter({
filterString: function() { filterString: function() {
return queryString.getParam('spec'); return queryString.getParam("spec")
} }
}); })
config.specFilter = function(spec) { config.specFilter = function(spec) {
return specFilter.matches(spec.getFullName()); return specFilter.matches(spec.getFullName())
}; }
env.configure(config); env.configure(config)
/** /**
* ## Execution * ## Execution
* *
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
*/ */
const currentWindowOnload = window.onload; const currentWindowOnload = window.onload
window.onload = function() { window.onload = function() {
if (currentWindowOnload) { if (currentWindowOnload) {
currentWindowOnload(); currentWindowOnload()
} }
htmlReporter.initialize(); htmlReporter.initialize()
env.execute(); env.execute()
}; }
})(); })()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -16,20 +16,20 @@ beforeEach(function () {
} }
}) })
}) })
describe('stable-diffusion-ui', function() { describe("stable-diffusion-ui", function() {
beforeEach(function() { beforeEach(function() {
expect(typeof SD).toBe('object') expect(typeof SD).toBe("object")
expect(typeof SD.serverState).toBe('object') expect(typeof SD.serverState).toBe("object")
expect(typeof SD.serverState.status).toBe('string') expect(typeof SD.serverState.status).toBe("string")
}) })
it('should be able to reach the backend', async function() { it("should be able to reach the backend", async function() {
expect(SD.serverState.status).toBe(SD.ServerStates.unavailable) expect(SD.serverState.status).toBe(SD.ServerStates.unavailable)
SD.sessionId = JASMINE_SESSION_ID SD.sessionId = JASMINE_SESSION_ID
await SD.init() await SD.init()
expect(SD.isServerAvailable()).toBeTrue() expect(SD.isServerAvailable()).toBeTrue()
}) })
it('enfore the current task state', function() { it("enfore the current task state", function() {
const task = new SD.Task() const task = new SD.Task()
expect(task.status).toBe(SD.TaskStatus.init) expect(task.status).toBe(SD.TaskStatus.init)
expect(task.isPending).toBeTrue() expect(task.isPending).toBeTrue()
@ -65,149 +65,161 @@ describe('stable-diffusion-ui', function() {
task._setStatus(SD.TaskStatus.completed) task._setStatus(SD.TaskStatus.completed)
}).toThrowError() }).toThrowError()
}) })
it('should be able to run tasks', async function() { it("should be able to run tasks", async function() {
expect(typeof SD.Task.run).toBe('function') expect(typeof SD.Task.run).toBe("function")
const promiseGenerator = (function*(val) { const promiseGenerator = (function*(val) {
expect(val).toBe('start') expect(val).toBe("start")
expect(yield 1 + 1).toBe(4) expect(yield 1 + 1).toBe(4)
expect(yield 2 + 2).toBe(8) expect(yield 2 + 2).toBe(8)
yield asyncDelay(500) yield asyncDelay(500)
expect(yield 3 + 3).toBe(12) expect(yield 3 + 3).toBe(12)
expect(yield 4 + 4).toBe(16) expect(yield 4 + 4).toBe(16)
return 8 + 8 return 8 + 8
})('start') })("start")
const callback = function({ value, done }) { const callback = function({ value, done }) {
return { value: 2 * value, done } return { value: 2 * value, done }
} }
expect(await SD.Task.run(promiseGenerator, { callback })).toBe(32) expect(await SD.Task.run(promiseGenerator, { callback })).toBe(32)
}) })
it('should be able to queue tasks', async function() { it("should be able to queue tasks", async function() {
expect(typeof SD.Task.enqueue).toBe('function') expect(typeof SD.Task.enqueue).toBe("function")
const promiseGenerator = (function*(val) { const promiseGenerator = (function*(val) {
expect(val).toBe('start') expect(val).toBe("start")
expect(yield 1 + 1).toBe(4) expect(yield 1 + 1).toBe(4)
expect(yield 2 + 2).toBe(8) expect(yield 2 + 2).toBe(8)
yield asyncDelay(500) yield asyncDelay(500)
expect(yield 3 + 3).toBe(12) expect(yield 3 + 3).toBe(12)
expect(yield 4 + 4).toBe(16) expect(yield 4 + 4).toBe(16)
return 8 + 8 return 8 + 8
})('start') })("start")
const callback = function({ value, done }) { const callback = function({ value, done }) {
return { value: 2 * value, done } return { value: 2 * value, done }
} }
const gen = SD.Task.asGenerator({ generator: promiseGenerator, callback }) const gen = SD.Task.asGenerator({ generator: promiseGenerator, callback })
expect(await SD.Task.enqueue(gen)).toBe(32) expect(await SD.Task.enqueue(gen)).toBe(32)
}) })
it('should be able to chain handlers', async function() { it("should be able to chain handlers", async function() {
expect(typeof SD.Task.enqueue).toBe('function') expect(typeof SD.Task.enqueue).toBe("function")
const promiseGenerator = (function*(val) { const promiseGenerator = (function*(val) {
expect(val).toBe('start') expect(val).toBe("start")
expect(yield {test: '1'}).toEqual({test: '1', foo: 'bar'}) expect(yield { test: "1" }).toEqual({ test: "1", foo: "bar" })
expect(yield 2 + 2).toEqual(8) expect(yield 2 + 2).toEqual(8)
yield asyncDelay(500) yield asyncDelay(500)
expect(yield 3 + 3).toEqual(12) expect(yield 3 + 3).toEqual(12)
expect(yield {test: 4}).toEqual({test: 8, foo: 'bar'}) expect(yield { test: 4 }).toEqual({ test: 8, foo: "bar" })
return { test: 8 } return { test: 8 }
})('start') })("start")
const gen1 = SD.Task.asGenerator({generator: promiseGenerator, callback: function({value, done}) { const gen1 = SD.Task.asGenerator({
generator: promiseGenerator,
callback: function({ value, done }) {
if (typeof value === "object") { if (typeof value === "object") {
value['foo'] = 'bar' value["foo"] = "bar"
} }
return { value, done } return { value, done }
}}) }
const gen2 = SD.Task.asGenerator({generator: gen1, callback: function({value, done}) { })
if (typeof value === 'number') { const gen2 = SD.Task.asGenerator({
generator: gen1,
callback: function({ value, done }) {
if (typeof value === "number") {
value = 2 * value value = 2 * value
} }
if (typeof value === 'object' && typeof value.test === 'number') { if (typeof value === "object" && typeof value.test === "number") {
value.test = 2 * value.test value.test = 2 * value.test
} }
return { value, done } return { value, done }
}}) }
expect(await SD.Task.enqueue(gen2)).toEqual({test:32, foo: 'bar'})
}) })
describe('ServiceContainer', function() { expect(await SD.Task.enqueue(gen2)).toEqual({ test: 32, foo: "bar" })
it('should be able to register providers', function() { })
describe("ServiceContainer", function() {
it("should be able to register providers", function() {
const cont = new ServiceContainer( const cont = new ServiceContainer(
function foo() { function foo() {
this.bar = '' this.bar = ""
}, },
function bar() { function bar() {
return () => 0 return () => 0
}, },
{ name: 'zero', definition: 0 }, { name: "zero", definition: 0 },
{ name: 'ctx', definition: () => Object.create(null), singleton: true }, { name: "ctx", definition: () => Object.create(null), singleton: true },
{ name: 'test', {
name: "test",
definition: (ctx, missing, one, foo) => { definition: (ctx, missing, one, foo) => {
expect(ctx).toEqual({ ran: true }) expect(ctx).toEqual({ ran: true })
expect(one).toBe(1) expect(one).toBe(1)
expect(typeof foo).toBe('object') expect(typeof foo).toBe("object")
expect(foo.bar).toBeDefined() expect(foo.bar).toBeDefined()
expect(typeof missing).toBe('undefined') expect(typeof missing).toBe("undefined")
return {foo: 'bar'} return { foo: "bar" }
}, dependencies: ['ctx', 'missing', 'one', 'foo'] },
dependencies: ["ctx", "missing", "one", "foo"]
} }
) )
const fooObj = cont.get('foo') const fooObj = cont.get("foo")
expect(typeof fooObj).toBe('object') expect(typeof fooObj).toBe("object")
fooObj.ran = true fooObj.ran = true
const ctx = cont.get('ctx') const ctx = cont.get("ctx")
expect(ctx).toEqual({}) expect(ctx).toEqual({})
ctx.ran = true ctx.ran = true
const bar = cont.get('bar') const bar = cont.get("bar")
expect(typeof bar).toBe('function') expect(typeof bar).toBe("function")
expect(bar()).toBe(0) expect(bar()).toBe(0)
cont.register({name: 'one', definition: 1}) cont.register({ name: "one", definition: 1 })
const test = cont.get('test') const test = cont.get("test")
expect(typeof test).toBe('object') expect(typeof test).toBe("object")
expect(test.foo).toBe('bar') expect(test.foo).toBe("bar")
}) })
}) })
it('should be able to stream data in chunks', async function() { it("should be able to stream data in chunks", async function() {
expect(SD.isServerAvailable()).toBeTrue() expect(SD.isServerAvailable()).toBeTrue()
const nbr_steps = 15 const nbr_steps = 15
let res = await fetch('/render', { let res = await fetch("/render", {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json"
}, },
body: JSON.stringify({ body: JSON.stringify({
"prompt": "a photograph of an astronaut riding a horse", prompt: "a photograph of an astronaut riding a horse",
"negative_prompt": "", negative_prompt: "",
"width": 128, width: 128,
"height": 128, height: 128,
"seed": Math.floor(Math.random() * 10000000), seed: Math.floor(Math.random() * 10000000),
"sampler": "plms", sampler: "plms",
"use_stable_diffusion_model": "sd-v1-4", use_stable_diffusion_model: "sd-v1-4",
"num_inference_steps": nbr_steps, num_inference_steps: nbr_steps,
"guidance_scale": 7.5, guidance_scale: 7.5,
"numOutputsParallel": 1, numOutputsParallel: 1,
"stream_image_progress": true, stream_image_progress: true,
"show_only_filtered_image": true, show_only_filtered_image: true,
"output_format": "jpeg", output_format: "jpeg",
"session_id": JASMINE_SESSION_ID, session_id: JASMINE_SESSION_ID
}), })
}) })
expect(res.ok).toBeTruthy() expect(res.ok).toBeTruthy()
const renderRequest = await res.json() const renderRequest = await res.json()
expect(typeof renderRequest.stream).toBe('string') expect(typeof renderRequest.stream).toBe("string")
expect(renderRequest.task).toBeDefined() expect(renderRequest.task).toBeDefined()
// Wait for server status to update. // Wait for server status to update.
await SD.waitUntil(() => { await SD.waitUntil(
console.log('Waiting for %s to be received...', renderRequest.task) () => {
return (!SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)]) console.log("Waiting for %s to be received...", renderRequest.task)
}, 250, 10 * 60 * 1000) return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)]
},
250,
10 * 60 * 1000
)
// Wait for task to start on server. // Wait for task to start on server.
await SD.waitUntil(() => { await SD.waitUntil(() => {
console.log('Waiting for %s to start...', renderRequest.task) console.log("Waiting for %s to start...", renderRequest.task)
return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)] !== 'pending' return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)] !== "pending"
}, 250) }, 250)
const reader = new SD.ChunkedStreamReader(renderRequest.stream) const reader = new SD.ChunkedStreamReader(renderRequest.stream)
@ -217,11 +229,11 @@ describe('stable-diffusion-ui', function() {
if (!value || value.length <= 0) { if (!value || value.length <= 0) {
return return
} }
return reader.readStreamAsJSON(value.join('')) return reader.readStreamAsJSON(value.join(""))
} }
reader.onNext = function({ done, value }) { reader.onNext = function({ done, value }) {
console.log(value) console.log(value)
if (typeof value === 'object' && 'status' in value) { if (typeof value === "object" && "status" in value) {
done = true done = true
} }
return { done, value } return { done, value }
@ -231,10 +243,10 @@ describe('stable-diffusion-ui', function() {
let complete = false let complete = false
//for await (const stepUpdate of reader) { //for await (const stepUpdate of reader) {
for await (const stepUpdate of reader.open()) { for await (const stepUpdate of reader.open()) {
console.log('ChunkedStreamReader received ', stepUpdate) console.log("ChunkedStreamReader received ", stepUpdate)
lastUpdate = stepUpdate lastUpdate = stepUpdate
if (complete) { if (complete) {
expect(stepUpdate.status).toBe('succeeded') expect(stepUpdate.status).toBe("succeeded")
expect(stepUpdate.output).toHaveSize(1) expect(stepUpdate.output).toHaveSize(1)
} else { } else {
expect(stepUpdate.total_steps).toBe(nbr_steps) expect(stepUpdate.total_steps).toBe(nbr_steps)
@ -250,33 +262,35 @@ describe('stable-diffusion-ui', function() {
res = await fetch(renderRequest.stream) res = await fetch(renderRequest.stream)
expect(res.ok).toBeTruthy() expect(res.ok).toBeTruthy()
const cachedResponse = await res.json() const cachedResponse = await res.json()
console.log('Cache test %s received %o', i, cachedResponse) console.log("Cache test %s received %o", i, cachedResponse)
expect(lastUpdate).toEqual(cachedResponse) expect(lastUpdate).toEqual(cachedResponse)
} }
}) })
describe('should be able to make renders', function() { describe("should be able to make renders", function() {
beforeEach(function() { beforeEach(function() {
expect(SD.isServerAvailable()).toBeTrue() expect(SD.isServerAvailable()).toBeTrue()
}) })
it('basic inline request', async function() { it("basic inline request", async function() {
let stepCount = 0 let stepCount = 0
let complete = false let complete = false
const result = await SD.render({ const result = await SD.render(
"prompt": "a photograph of an astronaut riding a horse", {
"width": 128, prompt: "a photograph of an astronaut riding a horse",
"height": 128, width: 128,
"num_inference_steps": 10, height: 128,
"show_only_filtered_image": false, num_inference_steps: 10,
show_only_filtered_image: false,
//"use_face_correction": 'GFPGANv1.3', //"use_face_correction": 'GFPGANv1.3',
"use_upscale": "RealESRGAN_x4plus", use_upscale: "RealESRGAN_x4plus",
"session_id": JASMINE_SESSION_ID, session_id: JASMINE_SESSION_ID
}, function(event) { },
function(event) {
console.log(this, event) console.log(this, event)
if ('update' in event) { if ("update" in event) {
const stepUpdate = event.update const stepUpdate = event.update
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) { if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
expect(stepUpdate.status).toBe('succeeded') expect(stepUpdate.status).toBe("succeeded")
expect(stepUpdate.output).toHaveSize(2) expect(stepUpdate.output).toHaveSize(2)
} else { } else {
expect(stepUpdate.step).toBe(stepCount) expect(stepUpdate.step).toBe(stepCount)
@ -287,29 +301,33 @@ describe('stable-diffusion-ui', function() {
} }
} }
} }
}) }
)
console.log(result) console.log(result)
expect(result.status).toBe('succeeded') expect(result.status).toBe("succeeded")
expect(result.output).toHaveSize(2) expect(result.output).toHaveSize(2)
}) })
it('post and reader request', async function() { it("post and reader request", async function() {
const renderTask = new SD.RenderTask({ const renderTask = new SD.RenderTask({
"prompt": "a photograph of an astronaut riding a horse", prompt: "a photograph of an astronaut riding a horse",
"width": 128, width: 128,
"height": 128, height: 128,
"seed": SD.MAX_SEED_VALUE, seed: SD.MAX_SEED_VALUE,
"num_inference_steps": 10, num_inference_steps: 10,
"session_id": JASMINE_SESSION_ID, session_id: JASMINE_SESSION_ID
}) })
expect(renderTask.status).toBe(SD.TaskStatus.init) expect(renderTask.status).toBe(SD.TaskStatus.init)
const timeout = -1 const timeout = -1
const renderRequest = await renderTask.post(timeout) const renderRequest = await renderTask.post(timeout)
expect(typeof renderRequest.stream).toBe('string') expect(typeof renderRequest.stream).toBe("string")
expect(renderTask.status).toBe(SD.TaskStatus.waiting) expect(renderTask.status).toBe(SD.TaskStatus.waiting)
expect(renderTask.streamUrl).toBe(renderRequest.stream) expect(renderTask.streamUrl).toBe(renderRequest.stream)
await renderTask.waitUntil({state: SD.TaskStatus.processing, callback: () => console.log('Waiting for render task to start...') }) await renderTask.waitUntil({
state: SD.TaskStatus.processing,
callback: () => console.log("Waiting for render task to start...")
})
expect(renderTask.status).toBe(SD.TaskStatus.processing) expect(renderTask.status).toBe(SD.TaskStatus.processing)
let stepCount = 0 let stepCount = 0
@ -318,7 +336,7 @@ describe('stable-diffusion-ui', function() {
for await (const stepUpdate of renderTask.reader.open()) { for await (const stepUpdate of renderTask.reader.open()) {
console.log(stepUpdate) console.log(stepUpdate)
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) { if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
expect(stepUpdate.status).toBe('succeeded') expect(stepUpdate.status).toBe("succeeded")
expect(stepUpdate.output).toHaveSize(1) expect(stepUpdate.output).toHaveSize(1)
} else { } else {
expect(stepUpdate.step).toBe(stepCount) expect(stepUpdate.step).toBe(stepCount)
@ -330,28 +348,28 @@ describe('stable-diffusion-ui', function() {
} }
} }
expect(renderTask.status).toBe(SD.TaskStatus.completed) expect(renderTask.status).toBe(SD.TaskStatus.completed)
expect(renderTask.result.status).toBe('succeeded') expect(renderTask.result.status).toBe("succeeded")
expect(renderTask.result.output).toHaveSize(1) expect(renderTask.result.output).toHaveSize(1)
}) })
it('queued request', async function() { it("queued request", async function() {
let stepCount = 0 let stepCount = 0
let complete = false let complete = false
const renderTask = new SD.RenderTask({ const renderTask = new SD.RenderTask({
"prompt": "a photograph of an astronaut riding a horse", prompt: "a photograph of an astronaut riding a horse",
"width": 128, width: 128,
"height": 128, height: 128,
"num_inference_steps": 10, num_inference_steps: 10,
"show_only_filtered_image": false, show_only_filtered_image: false,
//"use_face_correction": 'GFPGANv1.3', //"use_face_correction": 'GFPGANv1.3',
"use_upscale": "RealESRGAN_x4plus", use_upscale: "RealESRGAN_x4plus",
"session_id": JASMINE_SESSION_ID, session_id: JASMINE_SESSION_ID
}) })
await renderTask.enqueue(function(event) { await renderTask.enqueue(function(event) {
console.log(this, event) console.log(this, event)
if ('update' in event) { if ("update" in event) {
const stepUpdate = event.update const stepUpdate = event.update
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) { if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
expect(stepUpdate.status).toBe('succeeded') expect(stepUpdate.status).toBe("succeeded")
expect(stepUpdate.output).toHaveSize(2) expect(stepUpdate.output).toHaveSize(2)
} else { } else {
expect(stepUpdate.step).toBe(stepCount) expect(stepUpdate.step).toBe(stepCount)
@ -364,12 +382,12 @@ describe('stable-diffusion-ui', function() {
} }
}) })
console.log(renderTask.result) console.log(renderTask.result)
expect(renderTask.result.status).toBe('succeeded') expect(renderTask.result.status).toBe("succeeded")
expect(renderTask.result.output).toHaveSize(2) expect(renderTask.result.output).toHaveSize(2)
}) })
}) })
describe('# Special cases', function() { describe("# Special cases", function() {
it('should throw an exception on set for invalid sessionId', function() { it("should throw an exception on set for invalid sessionId", function() {
expect(function() { expect(function() {
SD.sessionId = undefined SD.sessionId = undefined
}).toThrowError("Can't set sessionId to undefined.") }).toThrowError("Can't set sessionId to undefined.")
@ -386,16 +404,17 @@ if (!PLUGINS.SELFTEST) {
PLUGINS.SELFTEST = {} PLUGINS.SELFTEST = {}
} }
loadUIPlugins().then(function() { loadUIPlugins().then(function() {
console.log('loadCompleted', loadEvent) console.log("loadCompleted", loadEvent)
describe('@Plugins', function() { describe("@Plugins", function() {
it('exposes hooks to overide', function() { it("exposes hooks to overide", function() {
expect(typeof PLUGINS.IMAGE_INFO_BUTTONS).toBe('object') expect(typeof PLUGINS.IMAGE_INFO_BUTTONS).toBe("object")
expect(typeof PLUGINS.TASK_CREATE).toBe('object') expect(typeof PLUGINS.TASK_CREATE).toBe("object")
}) })
describe('supports selftests', function() { // Hook to allow plugins to define tests. describe("supports selftests", function() {
// Hook to allow plugins to define tests.
const pluginsTests = Object.keys(PLUGINS.SELFTEST).filter((key) => PLUGINS.SELFTEST.hasOwnProperty(key)) const pluginsTests = Object.keys(PLUGINS.SELFTEST).filter((key) => PLUGINS.SELFTEST.hasOwnProperty(key))
if (!pluginsTests || pluginsTests.length <= 0) { if (!pluginsTests || pluginsTests.length <= 0) {
it('but nothing loaded...', function() { it("but nothing loaded...", function() {
expect(true).toBeTruthy() expect(true).toBeTruthy()
}) })
return return

View File

@ -1,4 +1,4 @@
(function() { ;(function() {
"use strict" "use strict"
///////////////////// Function section ///////////////////// Function section
@ -18,49 +18,54 @@
return y return y
} }
function getCurrentTime() { function getCurrentTime() {
const now = new Date(); const now = new Date()
let hours = now.getHours(); let hours = now.getHours()
let minutes = now.getMinutes(); let minutes = now.getMinutes()
let seconds = now.getSeconds(); let seconds = now.getSeconds()
hours = hours < 10 ? `0${hours}` : hours; hours = hours < 10 ? `0${hours}` : hours
minutes = minutes < 10 ? `0${minutes}` : minutes; minutes = minutes < 10 ? `0${minutes}` : minutes
seconds = seconds < 10 ? `0${seconds}` : seconds; seconds = seconds < 10 ? `0${seconds}` : seconds
return `${hours}:${minutes}:${seconds}`; return `${hours}:${minutes}:${seconds}`
} }
function addLogMessage(message) { function addLogMessage(message) {
const logContainer = document.getElementById('merge-log'); const logContainer = document.getElementById("merge-log")
logContainer.innerHTML += `<i>${getCurrentTime()}</i> ${message}<br>`; logContainer.innerHTML += `<i>${getCurrentTime()}</i> ${message}<br>`
// Scroll to the bottom of the log // Scroll to the bottom of the log
logContainer.scrollTop = logContainer.scrollHeight; logContainer.scrollTop = logContainer.scrollHeight
document.querySelector('#merge-log-container').style.display = 'block' document.querySelector("#merge-log-container").style.display = "block"
} }
function addLogSeparator() { function addLogSeparator() {
const logContainer = document.getElementById('merge-log'); const logContainer = document.getElementById("merge-log")
logContainer.innerHTML += '<hr>' logContainer.innerHTML += "<hr>"
logContainer.scrollTop = logContainer.scrollHeight; logContainer.scrollTop = logContainer.scrollHeight
} }
function drawDiagram(fn) { function drawDiagram(fn) {
const SIZE = 300 const SIZE = 300
const canvas = document.getElementById('merge-canvas'); const canvas = document.getElementById("merge-canvas")
canvas.height = canvas.width = SIZE canvas.height = canvas.width = SIZE
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext("2d")
// Draw coordinate system // Draw coordinate system
ctx.scale(1, -1); ctx.scale(1, -1)
ctx.translate(0, -canvas.height); ctx.translate(0, -canvas.height)
ctx.lineWidth = 1; ctx.lineWidth = 1
ctx.beginPath(); ctx.beginPath()
ctx.strokeStyle = 'white' ctx.strokeStyle = "white"
ctx.moveTo(0,0); ctx.lineTo(0,SIZE); ctx.lineTo(SIZE,SIZE); ctx.lineTo(SIZE,0); ctx.lineTo(0,0); ctx.lineTo(SIZE,SIZE); ctx.moveTo(0, 0)
ctx.lineTo(0, SIZE)
ctx.lineTo(SIZE, SIZE)
ctx.lineTo(SIZE, 0)
ctx.lineTo(0, 0)
ctx.lineTo(SIZE, SIZE)
ctx.stroke() ctx.stroke()
ctx.beginPath() ctx.beginPath()
ctx.setLineDash([1, 2]) ctx.setLineDash([1, 2])
@ -74,29 +79,29 @@
ctx.stroke() ctx.stroke()
ctx.beginPath() ctx.beginPath()
ctx.setLineDash([]) ctx.setLineDash([])
ctx.beginPath(); ctx.beginPath()
ctx.strokeStyle = 'black' ctx.strokeStyle = "black"
ctx.lineWidth = 3; ctx.lineWidth = 3
// Plot function // Plot function
const numSamples = 20; const numSamples = 20
for (let i = 0; i <= numSamples; i++) { for (let i = 0; i <= numSamples; i++) {
const x = i / numSamples; const x = i / numSamples
const y = fn(x); const y = fn(x)
const canvasX = x * SIZE; const canvasX = x * SIZE
const canvasY = y * SIZE; const canvasY = y * SIZE
if (i === 0) { if (i === 0) {
ctx.moveTo(canvasX, canvasY); ctx.moveTo(canvasX, canvasY)
} else { } else {
ctx.lineTo(canvasX, canvasY); ctx.lineTo(canvasX, canvasY)
} }
} }
ctx.stroke() ctx.stroke()
// Plot alpha values (yellow boxes) // Plot alpha values (yellow boxes)
let start = parseFloat( document.querySelector('#merge-start').value ) let start = parseFloat(document.querySelector("#merge-start").value)
let step = parseFloat( document.querySelector('#merge-step').value ) let step = parseFloat(document.querySelector("#merge-step").value)
let iterations = document.querySelector('#merge-count').value>>0 let iterations = document.querySelector("#merge-count").value >> 0
ctx.beginPath() ctx.beginPath()
ctx.fillStyle = "yellow" ctx.fillStyle = "yellow"
for (let i = 0; i < iterations; i++) { for (let i = 0; i < iterations; i++) {
@ -107,33 +112,38 @@
ctx.rect(x - 3, y - 3, 6, 6) ctx.rect(x - 3, y - 3, 6, 6)
ctx.fill() ctx.fill()
} else { } else {
ctx.strokeStyle = 'red' ctx.strokeStyle = "red"
ctx.moveTo(0,0); ctx.lineTo(0,SIZE); ctx.lineTo(SIZE,SIZE); ctx.lineTo(SIZE,0); ctx.lineTo(0,0); ctx.lineTo(SIZE,SIZE); ctx.moveTo(0, 0)
ctx.lineTo(0, SIZE)
ctx.lineTo(SIZE, SIZE)
ctx.lineTo(SIZE, 0)
ctx.lineTo(0, 0)
ctx.lineTo(SIZE, SIZE)
ctx.stroke() ctx.stroke()
addLogMessage('<i>Warning: maximum ratio is &#8805; 100%</i>') addLogMessage("<i>Warning: maximum ratio is &#8805; 100%</i>")
} }
} }
} }
function updateChart() { function updateChart() {
let fn = (x) => x let fn = (x) => x
switch (document.querySelector('#merge-interpolation').value) { switch (document.querySelector("#merge-interpolation").value) {
case 'SmoothStep': case "SmoothStep":
fn = smoothstep fn = smoothstep
break break
case 'SmootherStep': case "SmootherStep":
fn = smootherstep fn = smootherstep
break break
case 'SmoothestStep': case "SmoothestStep":
fn = smootheststep fn = smootheststep
break break
} }
drawDiagram(fn) drawDiagram(fn)
} }
createTab({ createTab({
id: 'merge', id: "merge",
icon: 'fa-code-merge', icon: "fa-code-merge",
label: 'Merge models', label: "Merge models",
css: ` css: `
#tab-content-merge .tab-content-inner { #tab-content-merge .tab-content-inner {
max-width: 100%; max-width: 100%;
@ -307,19 +317,19 @@
return return
} }
const tabSettingsSingle = document.querySelector('#tab-merge-opts-single') const tabSettingsSingle = document.querySelector("#tab-merge-opts-single")
const tabSettingsBatch = document.querySelector('#tab-merge-opts-batch') const tabSettingsBatch = document.querySelector("#tab-merge-opts-batch")
linkTabContents(tabSettingsSingle) linkTabContents(tabSettingsSingle)
linkTabContents(tabSettingsBatch) linkTabContents(tabSettingsBatch)
console.log('Activate') console.log("Activate")
let mergeModelAField = new ModelDropdown(document.querySelector('#mergeModelA'), 'stable-diffusion') let mergeModelAField = new ModelDropdown(document.querySelector("#mergeModelA"), "stable-diffusion")
let mergeModelBField = new ModelDropdown(document.querySelector('#mergeModelB'), 'stable-diffusion') let mergeModelBField = new ModelDropdown(document.querySelector("#mergeModelB"), "stable-diffusion")
updateChart() updateChart()
// slider // slider
const singleMergeRatioField = document.querySelector('#single-merge-ratio') const singleMergeRatioField = document.querySelector("#single-merge-ratio")
const singleMergeRatioSlider = document.querySelector('#single-merge-ratio-slider') const singleMergeRatioSlider = document.querySelector("#single-merge-ratio-slider")
function updateSingleMergeRatio() { function updateSingleMergeRatio() {
singleMergeRatioField.value = singleMergeRatioSlider.value / 10 singleMergeRatioField.value = singleMergeRatioSlider.value / 10
@ -337,21 +347,21 @@
singleMergeRatioSlider.dispatchEvent(new Event("change")) singleMergeRatioSlider.dispatchEvent(new Event("change"))
} }
singleMergeRatioSlider.addEventListener('input', updateSingleMergeRatio) singleMergeRatioSlider.addEventListener("input", updateSingleMergeRatio)
singleMergeRatioField.addEventListener('input', updateSingleMergeRatioSlider) singleMergeRatioField.addEventListener("input", updateSingleMergeRatioSlider)
updateSingleMergeRatio() updateSingleMergeRatio()
document.querySelector('.merge-config').addEventListener('change', updateChart) document.querySelector(".merge-config").addEventListener("change", updateChart)
document.querySelector('#merge-button').addEventListener('click', async function(e) { document.querySelector("#merge-button").addEventListener("click", async function(e) {
// Build request template // Build request template
let model0 = mergeModelAField.value let model0 = mergeModelAField.value
let model1 = mergeModelBField.value let model1 = mergeModelBField.value
let request = { model0: model0, model1: model1 } let request = { model0: model0, model1: model1 }
request['use_fp16'] = document.querySelector('#merge-fp').value == 'fp16' request["use_fp16"] = document.querySelector("#merge-fp").value == "fp16"
let iterations = document.querySelector('#merge-count').value>>0 let iterations = document.querySelector("#merge-count").value >> 0
let start = parseFloat( document.querySelector('#merge-start').value ) let start = parseFloat(document.querySelector("#merge-start").value)
let step = parseFloat( document.querySelector('#merge-step').value ) let step = parseFloat(document.querySelector("#merge-step").value)
if (isTabActive(tabSettingsSingle)) { if (isTabActive(tabSettingsSingle)) {
start = parseFloat(singleMergeRatioField.value) start = parseFloat(singleMergeRatioField.value)
@ -364,27 +374,27 @@
} }
if (start + (iterations - 1) * step >= 100) { if (start + (iterations - 1) * step >= 100) {
addLogMessage('<i>Aborting: maximum ratio is &#8805; 100%</i>') addLogMessage("<i>Aborting: maximum ratio is &#8805; 100%</i>")
addLogMessage('Reduce the number of variations or the step size') addLogMessage("Reduce the number of variations or the step size")
addLogSeparator() addLogSeparator()
document.querySelector('#merge-count').focus() document.querySelector("#merge-count").focus()
return return
} }
if (document.querySelector('#merge-filename').value == "") { if (document.querySelector("#merge-filename").value == "") {
addLogMessage('<i>Aborting: No output file name specified</i>') addLogMessage("<i>Aborting: No output file name specified</i>")
addLogSeparator() addLogSeparator()
document.querySelector('#merge-filename').focus() document.querySelector("#merge-filename").focus()
return return
} }
// Disable merge button // Disable merge button
e.target.disabled = true e.target.disabled = true
e.target.classList.add('disabled') e.target.classList.add("disabled")
let cursor = $("body").css("cursor"); let cursor = $("body").css("cursor")
let label = document.querySelector('#merge-button').innerHTML let label = document.querySelector("#merge-button").innerHTML
$("body").css("cursor", "progress"); $("body").css("cursor", "progress")
document.querySelector('#merge-button').innerHTML = 'Merging models ...' document.querySelector("#merge-button").innerHTML = "Merging models ..."
addLogMessage("Merging models") addLogMessage("Merging models")
addLogMessage("Model A: " + model0) addLogMessage("Model A: " + model0)
@ -393,46 +403,48 @@
// Batch main loop // Batch main loop
for (let i = 0; i < iterations; i++) { for (let i = 0; i < iterations; i++) {
let alpha = (start + i * step) / 100 let alpha = (start + i * step) / 100
switch (document.querySelector('#merge-interpolation').value) { switch (document.querySelector("#merge-interpolation").value) {
case 'SmoothStep': case "SmoothStep":
alpha = smoothstep(alpha) alpha = smoothstep(alpha)
break break
case 'SmootherStep': case "SmootherStep":
alpha = smootherstep(alpha) alpha = smootherstep(alpha)
break break
case 'SmoothestStep': case "SmoothestStep":
alpha = smootheststep(alpha) alpha = smootheststep(alpha)
break break
} }
addLogMessage(`merging batch job ${i + 1}/${iterations}, alpha = ${alpha.toFixed(5)}...`) addLogMessage(`merging batch job ${i + 1}/${iterations}, alpha = ${alpha.toFixed(5)}...`)
request['out_path'] = document.querySelector('#merge-filename').value request["out_path"] = document.querySelector("#merge-filename").value
request['out_path'] += '-' + alpha.toFixed(5) + '.' + document.querySelector('#merge-format').value request["out_path"] += "-" + alpha.toFixed(5) + "." + document.querySelector("#merge-format").value
addLogMessage(`&nbsp;&nbsp;filename: ${request['out_path']}`) addLogMessage(`&nbsp;&nbsp;filename: ${request["out_path"]}`)
request['ratio'] = alpha request["ratio"] = alpha
let res = await fetch('/model/merge', { let res = await fetch("/model/merge", {
method: 'POST', method: "POST",
headers: { 'Content-Type': 'application/json' }, headers: { "Content-Type": "application/json" },
body: JSON.stringify(request) }) body: JSON.stringify(request)
const data = await res.json(); })
const data = await res.json()
addLogMessage(JSON.stringify(data)) addLogMessage(JSON.stringify(data))
} }
addLogMessage("<b>Done.</b> The models have been saved to your <tt>models/stable-diffusion</tt> folder.") addLogMessage(
"<b>Done.</b> The models have been saved to your <tt>models/stable-diffusion</tt> folder."
)
addLogSeparator() addLogSeparator()
// Re-enable merge button // Re-enable merge button
$("body").css("cursor", cursor); $("body").css("cursor", cursor)
document.querySelector('#merge-button').innerHTML = label document.querySelector("#merge-button").innerHTML = label
e.target.disabled = false e.target.disabled = false
e.target.classList.remove('disabled') e.target.classList.remove("disabled")
// Update model list // Update model list
stableDiffusionModelField.innerHTML = '' stableDiffusionModelField.innerHTML = ""
vaeModelField.innerHTML = '' vaeModelField.innerHTML = ""
hypernetworkModelField.innerHTML = '' hypernetworkModelField.innerHTML = ""
await getModels() await getModels()
}) })
}, }
}) })
})() })()

View File

@ -1,15 +1,15 @@
(function () { ;(function() {
"use strict" "use strict"
var styleSheet = document.createElement("style"); var styleSheet = document.createElement("style")
styleSheet.textContent = ` styleSheet.textContent = `
.modifier-card-tiny.modifier-toggle-inactive { .modifier-card-tiny.modifier-toggle-inactive {
background: transparent; background: transparent;
border: 2px dashed red; border: 2px dashed red;
opacity:0.2; opacity:0.2;
} }
`; `
document.head.appendChild(styleSheet); document.head.appendChild(styleSheet)
// observe for changes in tag list // observe for changes in tag list
var observer = new MutationObserver(function(mutations) { var observer = new MutationObserver(function(mutations) {
@ -25,28 +25,28 @@
}) })
function ModifierToggle() { function ModifierToggle() {
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay') let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay")
overlays.forEach (i => { overlays.forEach((i) => {
i.oncontextmenu = (e) => { i.oncontextmenu = (e) => {
e.preventDefault() e.preventDefault()
if (i.parentElement.classList.contains('modifier-toggle-inactive')) { if (i.parentElement.classList.contains("modifier-toggle-inactive")) {
i.parentElement.classList.remove('modifier-toggle-inactive') i.parentElement.classList.remove("modifier-toggle-inactive")
} } else {
else i.parentElement.classList.add("modifier-toggle-inactive")
{
i.parentElement.classList.add('modifier-toggle-inactive')
} }
// refresh activeTags // refresh activeTags
let modifierName = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].dataset.fullName let modifierName = i.parentElement
activeTags = activeTags.map(obj => { .getElementsByClassName("modifier-card-label")[0]
.getElementsByTagName("p")[0].dataset.fullName
activeTags = activeTags.map((obj) => {
if (trimModifiers(obj.name) === trimModifiers(modifierName)) { if (trimModifiers(obj.name) === trimModifiers(modifierName)) {
return {...obj, inactive: (obj.element.classList.contains('modifier-toggle-inactive'))}; return { ...obj, inactive: obj.element.classList.contains("modifier-toggle-inactive") }
} }
return obj; return obj
}); })
document.dispatchEvent(new Event('refreshImageModifiers')) document.dispatchEvent(new Event("refreshImageModifiers"))
} }
}) })
} }

View File

@ -1,17 +1,19 @@
(function() { ;(function() {
// Register selftests when loaded by jasmine. // Register selftests when loaded by jasmine.
if (typeof PLUGINS?.SELFTEST === 'object') { if (typeof PLUGINS?.SELFTEST === "object") {
PLUGINS.SELFTEST["release-notes"] = function() { PLUGINS.SELFTEST["release-notes"] = function() {
it('should be able to fetch CHANGES.md', async function() { it("should be able to fetch CHANGES.md", async function() {
let releaseNotes = await fetch(`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/main/CHANGES.md`) let releaseNotes = await fetch(
`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/main/CHANGES.md`
)
expect(releaseNotes.status).toBe(200) expect(releaseNotes.status).toBe(200)
}) })
} }
} }
createTab({ createTab({
id: 'news', id: "news",
icon: 'fa-bolt', icon: "fa-bolt",
label: "What's new", label: "What's new",
css: ` css: `
#tab-content-news .tab-content-inner { #tab-content-news .tab-content-inner {
@ -22,20 +24,22 @@
`, `,
onOpen: async ({ firstOpen }) => { onOpen: async ({ firstOpen }) => {
if (firstOpen) { if (firstOpen) {
const loadMarkedScriptPromise = loadScript('/media/js/marked.min.js') const loadMarkedScriptPromise = loadScript("/media/js/marked.min.js")
let appConfig = await fetch('/get/app_config') let appConfig = await fetch("/get/app_config")
if (!appConfig.ok) { if (!appConfig.ok) {
console.error('[release-notes] Failed to get app_config.') console.error("[release-notes] Failed to get app_config.")
return return
} }
appConfig = await appConfig.json() appConfig = await appConfig.json()
const updateBranch = appConfig.update_branch || 'main' const updateBranch = appConfig.update_branch || "main"
let releaseNotes = await fetch(`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/${updateBranch}/CHANGES.md`) let releaseNotes = await fetch(
`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/${updateBranch}/CHANGES.md`
)
if (!releaseNotes.ok) { if (!releaseNotes.ok) {
console.error('[release-notes] Failed to get CHANGES.md.') console.error("[release-notes] Failed to get CHANGES.md.")
return return
} }
releaseNotes = await releaseNotes.text() releaseNotes = await releaseNotes.text()
@ -44,6 +48,6 @@
return marked.parse(releaseNotes) return marked.parse(releaseNotes)
} }
}, }
}) })
})() })()

View File

@ -1,6 +1,7 @@
/* SD-UI Selftest Plugin.js /* SD-UI Selftest Plugin.js
*/ */
(function() { "use strict" ;(function() {
"use strict"
const ID_PREFIX = "selftest-plugin" const ID_PREFIX = "selftest-plugin"
const links = document.getElementById("community-links") const links = document.getElementById("community-links")
@ -10,16 +11,18 @@
} }
// Add link to Jasmine SpecRunner // Add link to Jasmine SpecRunner
const pluginLink = document.createElement('li') const pluginLink = document.createElement("li")
const options = { const options = {
'stopSpecOnExpectationFailure': "true", stopSpecOnExpectationFailure: "true",
'stopOnSpecFailure': 'false', stopOnSpecFailure: "false",
'random': 'false', random: "false",
'hideDisabled': 'false' hideDisabled: "false"
} }
const optStr = Object.entries(options).map(([key, val]) => `${key}=${val}`).join('&') const optStr = Object.entries(options)
.map(([key, val]) => `${key}=${val}`)
.join("&")
pluginLink.innerHTML = `<a id="${ID_PREFIX}-starttest" href="${location.protocol}/plugins/core/SpecRunner.html?${optStr}" target="_blank"><i class="fa-solid fa-vial-circle-check"></i> Start SelfTest</a>` pluginLink.innerHTML = `<a id="${ID_PREFIX}-starttest" href="${location.protocol}/plugins/core/SpecRunner.html?${optStr}" target="_blank"><i class="fa-solid fa-vial-circle-check"></i> Start SelfTest</a>`
links.appendChild(pluginLink) links.appendChild(pluginLink)
console.log('%s loaded!', ID_PREFIX) console.log("%s loaded!", ID_PREFIX)
})() })()

8
yarn.lock Normal file
View File

@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
prettier@^1.19.1:
version "1.19.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==