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,24 +55,24 @@ 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 = [
{ id: "editor-inputs", name: "Prompt" }, // gets the "keys" property filled in with an ordered list of settings in this section via initSettings
{ id: "editor-inputs", name: "Prompt" },
{ id: "editor-settings", name: "Image Settings" }, { id: "editor-settings", name: "Image Settings" },
{ id: "system-settings", name: "System Settings" }, { id: "system-settings", name: "System Settings" },
{ id: "container", name: "Other" } { id: "container", name: "Other" }
] ]
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,25 +1,25 @@
"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()) {
case "true": case "true":
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,22 +158,26 @@ 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)
}, },
readUI: () => applyColorCorrectionField.checked, readUI: () => applyColorCorrectionField.checked,
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(
if (Boolean(task.reqBody.mask)) { "load",
imageInpainter.setImg(task.reqBody.mask) function() {
maskSetting.checked = true if (Boolean(task.reqBody.mask)) {
} imageInpainter.setImg(task.reqBody.mask)
}, { once: true }) maskSetting.checked = 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,73 +612,72 @@ 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
} }
}) })
// Adds a copy and a paste icon if the browser grants permission to write to clipboard. // Adds a copy and a paste icon if the browser grants permission to write to clipboard.
function checkWriteToClipboardPermission (result) { function checkWriteToClipboardPermission(result) {
if (result.state != "granted" && result.state != "prompt") { if (result.state != "granted" && result.state != "prompt") {
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,8 +690,8 @@ 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

File diff suppressed because it is too large Load Diff

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")
modifierCard.classList.remove(activeCardClass) .forEach((modifierCard) => {
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+' const modifierName = modifierCard.querySelector(".modifier-card-label p").dataset.fullName // pick the full modifier name
} if (activeTags.map((x) => x.name).includes(modifierName)) {
}) modifierCard.classList.remove(activeCardClass)
modifierCard.querySelector(".modifier-card-image-overlay").innerText = "+"
}
})
activeTags = [] 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")
if (trimModifiers(tag) == trimModifiers(modifierName)) { .forEach((modifierCard) => {
// add modifier to active array const modifierName = modifierCard.querySelector(".modifier-card-label p").dataset.fullName
if (!activeTags.map(x => x.name).includes(tag)) { // only add each tag once even if several custom modifier cards share the same tag const shortModifierName = modifierCard.querySelector(".modifier-card-label p").innerText
const imageModifierCard = modifierCard.cloneNode(true) if (trimModifiers(tag) == trimModifiers(modifierName)) {
imageModifierCard.querySelector('.modifier-card-label p').innerText = tag.replace(modifierName, shortModifierName) // add modifier to active array
activeTags.push({ if (!activeTags.map((x) => x.name).includes(tag)) {
'name': tag, // only add each tag once even if several custom modifier cards share the same tag
'element': imageModifierCard, const imageModifierCard = modifierCard.cloneNode(true)
'originElement': modifierCard imageModifierCard.querySelector(".modifier-card-label p").innerText = tag.replace(
}) modifierName,
shortModifierName
)
activeTags.push({
name: tag,
element: imageModifierCard,
originElement: modifierCard
})
}
modifierCard.classList.add(activeCardClass)
modifierCard.querySelector(".modifier-card-image-overlay").innerText = "-"
found = true
} }
modifierCard.classList.add(activeCardClass) })
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-' if (found == false) {
found = true // 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,88 +279,91 @@ 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) => {
if(makeActive) { const name = card.querySelector(".modifier-card-label").innerText
card.classList.add(activeCardClass) if (
card.querySelector('.modifier-card-image-overlay').innerText = '-' trimModifiers(modifierName) == trimModifiers(name) ||
trimModifiers(modifierName) == "by " + trimModifiers(name)
) {
if (makeActive) {
card.classList.add(activeCardClass)
card.querySelector(".modifier-card-image-overlay").innerText = "-"
} else {
card.classList.remove(activeCardClass)
card.querySelector(".modifier-card-image-overlay").innerText = "+"
}
} }
else{ })
card.classList.remove(activeCardClass)
card.querySelector('.modifier-card-image-overlay').innerText = '+'
}
}
})
} }
function changePreviewImages(val) { 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,23 +121,24 @@ 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:
"<b>Balanced:</b> nearly as fast as High, much lower VRAM usage<br/>" + "Faster performance requires more GPU memory (VRAM)<br/><br/>" +
"<b>High:</b> fastest, maximum GPU memory usage</br>" + "<b>Balanced:</b> nearly as fast as High, much lower VRAM usage<br/>" +
"<b>Low:</b> slowest, recommended for GPUs with 3 to 4 GB memory", "<b>High:</b> fastest, maximum GPU memory usage</br>" +
"<b>Low:</b> slowest, recommended for GPUs with 3 to 4 GB memory",
icon: "fa-forward", icon: "fa-forward",
default: "balanced", default: "balanced",
options: [ options: [
{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,37 +228,39 @@ 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"))
} }
} }
/** /**
* @param {Parameter} parameter * @param {Parameter} parameter
* @returns {string | HTMLElement} * @returns {string | HTMLElement}
*/ */
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,86 +560,90 @@ 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
} }
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

@ -3,7 +3,7 @@ const PLUGIN_API_VERSION = "1.0"
const PLUGINS = { const PLUGINS = {
/** /**
* Register new buttons to show on each output image. * Register new buttons to show on each output image.
* *
* Example: * Example:
* PLUGINS['IMAGE_INFO_BUTTONS'].push({ * PLUGINS['IMAGE_INFO_BUTTONS'].push({
* text: 'Make a Similar Image', * text: 'Make a Similar Image',
@ -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,14 +21,13 @@ 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")
modelResult //= document.querySelector("#model-result") modelResult //= document.querySelector("#model-result")
modelNoResult //= document.querySelector("#model-no-result") modelNoResult //= document.querySelector("#model-no-result")
currentSelection //= { elem: undefined, value: '', path: ''} currentSelection //= { elem: undefined, value: '', path: ''}
highlightedModelEntry //= undefined highlightedModelEntry //= undefined
activeModel //= undefined activeModel //= undefined
@ -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)
@ -82,21 +81,26 @@ 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(
// reload the models "refreshModels",
this.inputModels = modelsOptions[this.modelKey] this.bind(function(e) {
this.populateModels() // reload the models
}, this)) this.inputModels = modelsOptions[this.modelKey]
this.populateModels()
}, 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,66 +130,67 @@ 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
} }
} }
selectPreviousFile() { selectPreviousFile() {
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()
} }
selectNextFile() { selectNextFile() {
this.selectModelEntry(this.getNextVisibleSibling(this.highlightedModelEntry)) this.selectModelEntry(this.getNextVisibleSibling(this.highlightedModelEntry))
this.modelFilter.select() this.modelFilter.select()
} }
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,201 +279,195 @@ 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()) {
this.selectFirstFile() this.selectFirstFile()
} }
//} //}
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()) {
this.selectLastFile() this.selectLastFile()
} }
//} //}
break break
default: default:
//console.log(e.key) //console.log(e.key)
} }
} }
modelListFocus() { modelListFocus() {
this.selectEntry() this.selectEntry()
this.showAllEntries() this.showAllEntries()
} }
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()
} }
} }
} }
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
} }
} }
} }
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()
} }
} }
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) {
const filter = this.modelFilter.value.toLowerCase() const filter = this.modelFilter.value.toLowerCase()
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 */
getElementDimensions(element) { getElementDimensions(element) {
// Clone the element // Clone the element
const clone = element.cloneNode(true) const clone = element.cloneNode(true)
// Copy the styles of the original element to the cloned element // Copy the styles of the original element to the cloned element
const originalStyles = window.getComputedStyle(element) const originalStyles = window.getComputedStyle(element)
for (let i = 0; i < originalStyles.length; i++) { for (let i = 0; i < originalStyles.length; i++) {
const property = originalStyles[i] const property = originalStyles[i]
clone.style[property] = originalStyles.getPropertyValue(property) clone.style[property] = originalStyles.getPropertyValue(property)
} }
// Set its visibility to hidden and display to inline-block // Set its visibility to hidden and display to inline-block
clone.style.visibility = "hidden" clone.style.visibility = "hidden"
clone.style.display = "inline-block" clone.style.display = "inline-block"
// Put the cloned element next to the original element // Put the cloned element next to the original element
element.parentNode.insertBefore(clone, element.nextSibling) element.parentNode.insertBefore(clone, element.nextSibling)
// Get its width and height // Get its width and height
const width = clone.offsetWidth const width = clone.offsetWidth
const height = clone.offsetHeight const height = clone.offsetHeight
// Remove it from the DOM // Remove it from the DOM
clone.remove() clone.remove()
// Return its width and height // Return its width and height
return { width, height } return { width, height }
} }
/** /**
* @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 = []
if(this.modelList !== undefined) { if (this.modelList !== undefined) {
this.modelList.remove() this.modelList.remove()
this.modelFilterArrow.remove() this.modelFilterArrow.remove()
} }
@ -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
}) })
@ -520,74 +519,62 @@ class ModelDropdown
/** /**
* @param {Array<string | object} modelTree * @param {Array<string | object} modelTree
* @param {string} folderName * @param {string} folderName
* @param {boolean} isRootFolder * @param {boolean} isRootFolder
* @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 => { THEMES.forEach((theme) => {
var new_option = document.createElement("option"); var new_option = document.createElement("option")
new_option.setAttribute("value", theme.key); new_option.setAttribute("value", theme.key)
new_option.innerText = theme.name; new_option.innerText = theme.name
themeField.appendChild(new_option); 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 = ""; //
var theme = THEMES.find(t => t.key == theme_key);
body.style = ""
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,33 +20,34 @@ 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])
} }
} }
@ -152,23 +155,23 @@ 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
close: iPosn, ? bracePair(tkns, iPosn + 1, n, lst)
commas: lst : {
}; close: iPosn,
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 : {
fn: and,
args: []
},
let dctParse = dctSofar
? dctSofar
: {
fn: and,
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, fn: and,
args: dctParse.args.concat(lstOR ? orTree(dctParse, lstOR[0], dctBrace.commas) : head)
lstOR = dctBrace && ( },
dctBrace.close lstOR ? lstOR[1] : tail
) && dctBrace.commas.length ? ( )
splitAt(dctBrace.close + 1, tkns)
) : null;
return andTree({
fn: and,
args: dctParse.args.concat(
lstOR ? (
orTree(dctParse, lstOR[0], dctBrace.commas)
) : 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 ? ( .map(function(ts) {
andTree(null, ts)[0] return ts.length > 1 ? andTree(null, ts)[0] : 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
return a.concat( ? lstHead.reduce(function(a, h) {
and(args.slice(1)).map(function (t) { return a.concat(
return h + t; and(args.slice(1)).map(function(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,12 +343,12 @@ 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, {
promise: {value: makeQuerablePromise(srcPromise), writable: false} promise: { value: makeQuerablePromise(srcPromise), writable: false }
}) })
} }
@ -375,7 +358,7 @@ function PromiseSource() {
* If `immediate` is passed, trigger the function on the leading edge, instead of the trailing. * If `immediate` is passed, trigger the function on the leading edge, instead of the trailing.
* @Returns a promise that will resolve to func return value. * @Returns a promise that will resolve to func return value.
*/ */
function debounce (func, wait, immediate) { function debounce(func, wait, immediate) {
if (typeof wait === "undefined") { if (typeof wait === "undefined") {
wait = 40 wait = 40
} }
@ -399,11 +382,11 @@ 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)
} }
timeout = setTimeout(function () { timeout = setTimeout(function() {
if (!immediate) { if (!immediate) {
applyFn(context, args) applyFn(context, args)
} }
@ -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
@ -473,13 +456,13 @@ function makeQuerablePromise(promise) {
let isResolved = false let isResolved = false
let resolvedValue = undefined let resolvedValue = undefined
const qurPro = promise.then( const qurPro = promise.then(
function(val){ function(val) {
isResolved = true isResolved = true
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(
try { evs.map((callback) => {
return Promise.resolve(callback.apply(SD, args)) try {
} catch (ex) { return Promise.resolve(callback.apply(SD, args))
return Promise.reject(ex) } catch (ex) {
} return Promise.reject(ex)
})) }
})
)
} }
} }
@ -590,7 +575,7 @@ class ServiceContainer {
constructor(...servicesParams) { constructor(...servicesParams) {
servicesParams.forEach(this.register.bind(this)) servicesParams.forEach(this.register.bind(this))
} }
get services () { get services() {
return this.#services return this.#services
} }
get singletons() { get singletons() {
@ -598,54 +583,52 @@ 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) {
opts.singleton = true opts.singleton = true
} }
this.#services.set(params.name, opts) this.#services.set(params.name, opts)
return Object.assign({name: params.name}, opts) return Object.assign({ name: params.name }, opts)
} }
get(name) { get(name) {
const ctorInfos = this.#services.get(name) const ctorInfos = this.#services.get(name)
if (!ctorInfos) { if (!ctorInfos) {
return return
} }
if(!ServiceContainer.isConstructor(ctorInfos.definition)) { if (!ServiceContainer.isConstructor(ctorInfos.definition)) {
return ctorInfos.definition return ctorInfos.definition
} }
if(!ctorInfos.singleton) { if (!ctorInfos.singleton) {
return this._createInstance(ctorInfos) return this._createInstance(ctorInfos)
} }
const singletonInstance = this.#singletons.get(name) const singletonInstance = this.#singletons.get(name)
if(singletonInstance) { if (singletonInstance) {
return singletonInstance return singletonInstance
} }
const newSingletonInstance = this._createInstance(ctorInfos) const newSingletonInstance = this._createInstance(ctorInfos)
@ -655,7 +638,7 @@ class ServiceContainer {
_getResolvedDependencies(service) { _getResolvedDependencies(service) {
let classDependencies = [] let classDependencies = []
if(service.dependencies) { if (service.dependencies) {
classDependencies = service.dependencies.map(this.get.bind(this)) classDependencies = service.dependencies.map(this.get.bind(this))
} }
return classDependencies return classDependencies
@ -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 {
@ -750,33 +737,33 @@ Array.prototype.addEventListener = function(method, callback) {
/** /**
* @param {CreateTabRequest} request * @param {CreateTabRequest} request
*/ */
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,28 +1,32 @@
(function () { ;(function() {
"use strict" "use strict"
let autoScroll = document.querySelector("#auto_scroll") let autoScroll = document.querySelector("#auto_scroll")
// 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,93 +1,116 @@
(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) {
// mutations.forEach(function (mutation) { // mutations.forEach(function (mutation) {
if (editorModifierTagsList.childNodes.length > 0) { if (editorModifierTagsList.childNodes.length > 0) {
ModifierDragAndDrop(editorModifierTagsList) ModifierDragAndDrop(editorModifierTagsList)
} }
// }) // })
}) })
observer.observe(editorModifierTagsList, { observer.observe(editorModifierTagsList, {
childList: true childList: true
}) })
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,35 +1,37 @@
(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
} }
// observe for changes in tag list // observe for changes in tag list
const observer = new MutationObserver(function (mutations) { const observer = new MutationObserver(function(mutations) {
// mutations.forEach(function (mutation) { // mutations.forEach(function (mutation) {
if (editorModifierTagsList.childNodes.length > 0) { if (editorModifierTagsList.childNodes.length > 0) {
ModifierMouseWheel(editorModifierTagsList) ModifierMouseWheel(editorModifierTagsList)
} }
// }) // })
}) })
observer.observe(editorModifierTagsList, { observer.observe(editorModifierTagsList, {
childList: true childList: true
}) })
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,39 +26,39 @@ 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
* *
* 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,100 +33,98 @@ 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
* *
* More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
*/ */
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 config = {
stopOnSpecFailure: queryString.getParam("stopOnSpecFailure"),
stopSpecOnExpectationFailure: queryString.getParam("stopSpecOnExpectationFailure"),
hideDisabled: queryString.getParam("hideDisabled")
} }
});
const filterSpecs = !!queryString.getParam('spec'); const random = queryString.getParam("random")
const config = { if (random !== undefined && random !== "") {
stopOnSpecFailure: queryString.getParam('stopOnSpecFailure'), config.random = random
stopSpecOnExpectationFailure: queryString.getParam(
'stopSpecOnExpectationFailure'
),
hideDisabled: queryString.getParam('hideDisabled')
};
const random = queryString.getParam('random');
if (random !== undefined && random !== '') {
config.random = random;
}
const seed = queryString.getParam('seed');
if (seed) {
config.seed = seed;
}
/**
* ## Reporters
* The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
*/
const htmlReporter = new jasmine.HtmlReporter({
env: env,
navigateWithNewParam: function(key, value) {
return queryString.navigateWithNewParam(key, value);
},
addToExistingQueryString: function(key, value) {
return queryString.fullStringWithNewParam(key, value);
},
getContainer: function() {
return document.body;
},
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
},
timer: new jasmine.Timer(),
filterSpecs: filterSpecs
});
/**
* 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(htmlReporter);
/**
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
*/
const specFilter = new jasmine.HtmlSpecFilter({
filterString: function() {
return queryString.getParam('spec');
} }
});
config.specFilter = function(spec) { const seed = queryString.getParam("seed")
return specFilter.matches(spec.getFullName()); if (seed) {
}; config.seed = seed
env.configure(config);
/**
* ## 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.
*/
const currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
} }
htmlReporter.initialize();
env.execute(); /**
}; * ## Reporters
})(); * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
*/
const htmlReporter = new jasmine.HtmlReporter({
env: env,
navigateWithNewParam: function(key, value) {
return queryString.navigateWithNewParam(key, value)
},
addToExistingQueryString: function(key, value) {
return queryString.fullStringWithNewParam(key, value)
},
getContainer: function() {
return document.body
},
createElement: function() {
return document.createElement.apply(document, arguments)
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments)
},
timer: new jasmine.Timer(),
filterSpecs: filterSpecs
})
/**
* 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(htmlReporter)
/**
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
*/
const specFilter = new jasmine.HtmlSpecFilter({
filterString: function() {
return queryString.getParam("spec")
}
})
config.specFilter = function(spec) {
return specFilter.matches(spec.getFullName())
}
env.configure(config)
/**
* ## 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.
*/
const currentWindowOnload = window.onload
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload()
}
htmlReporter.initialize()
env.execute()
}
})()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,12 @@
const JASMINE_SESSION_ID = `jasmine-${String(Date.now()).slice(8)}` const JASMINE_SESSION_ID = `jasmine-${String(Date.now()).slice(8)}`
beforeEach(function () { beforeEach(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 60 * 1000 // Test timeout after 15 minutes jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 60 * 1000 // Test timeout after 15 minutes
jasmine.addMatchers({ jasmine.addMatchers({
toBeOneOf: function () { toBeOneOf: function() {
return { return {
compare: function (actual, expected) { compare: function(actual, expected) {
return { return {
pass: expected.includes(actual) pass: expected.includes(actual)
} }
@ -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({
if (typeof value === "object") { generator: promiseGenerator,
value['foo'] = 'bar' callback: function({ value, done }) {
if (typeof value === "object") {
value["foo"] = "bar"
}
return { value, done }
} }
return {value, done} })
}}) const gen2 = SD.Task.asGenerator({
const gen2 = SD.Task.asGenerator({generator: gen1, callback: function({value, done}) { generator: gen1,
if (typeof value === 'number') { callback: function({ value, done }) {
value = 2 * value if (typeof value === "number") {
value = 2 * value
}
if (typeof value === "object" && typeof value.test === "number") {
value.test = 2 * value.test
}
return { value, done }
} }
if (typeof value === 'object' && typeof value.test === 'number') { })
value.test = 2 * value.test expect(await SD.Task.enqueue(gen2)).toEqual({ test: 32, foo: "bar" })
}
return {value, done}
}})
expect(await SD.Task.enqueue(gen2)).toEqual({test:32, foo: 'bar'})
}) })
describe('ServiceContainer', function() { describe("ServiceContainer", function() {
it('should be able to register providers', 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,24 +229,24 @@ 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 }
} }
let lastUpdate = undefined let lastUpdate = undefined
let stepCount = 0 let stepCount = 0
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)
@ -246,70 +258,76 @@ describe('stable-diffusion-ui', function() {
} }
} }
} }
for(let i=1; i <= 5; ++i) { for (let i = 1; i <= 5; ++i) {
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,
//"use_face_correction": 'GFPGANv1.3', show_only_filtered_image: false,
"use_upscale": "RealESRGAN_x4plus", //"use_face_correction": 'GFPGANv1.3',
"session_id": JASMINE_SESSION_ID, use_upscale: "RealESRGAN_x4plus",
}, function(event) { session_id: JASMINE_SESSION_ID
console.log(this, event) },
if ('update' in event) { function(event) {
const stepUpdate = event.update console.log(this, event)
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) { if ("update" in event) {
expect(stepUpdate.status).toBe('succeeded') const stepUpdate = event.update
expect(stepUpdate.output).toHaveSize(2) if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
} else { expect(stepUpdate.status).toBe("succeeded")
expect(stepUpdate.step).toBe(stepCount) expect(stepUpdate.output).toHaveSize(2)
if (stepUpdate.step === stepUpdate.total_steps) {
complete = true
} else { } else {
stepCount++ expect(stepUpdate.step).toBe(stepCount)
if (stepUpdate.step === stepUpdate.total_steps) {
complete = true
} else {
stepCount++
}
} }
} }
} }
}) )
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,122 +18,132 @@
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])
const n = SIZE / 10 const n = SIZE / 10
for (let i=n; i<SIZE; i+=n) { for (let i = n; i < SIZE; i += n) {
ctx.moveTo(0,i) ctx.moveTo(0, i)
ctx.lineTo(SIZE,i) ctx.lineTo(SIZE, i)
ctx.moveTo(i,0) ctx.moveTo(i, 0)
ctx.lineTo(i,SIZE) ctx.lineTo(i, SIZE)
} }
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++) {
const alpha = ( start + i * step ) / 100 const alpha = (start + i * step) / 100
const x = alpha*SIZE const x = alpha * SIZE
const y = fn(alpha) * SIZE const y = fn(alpha) * SIZE
if (x <= SIZE) { if (x <= SIZE) {
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)
@ -363,76 +373,78 @@
addLogMessage(`step = ${step}%`) addLogMessage(`step = ${step}%`)
} }
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)
addLogMessage("Model B: "+model1) addLogMessage("Model B: " + model1)
// 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,52 +1,52 @@
(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) {
// mutations.forEach(function (mutation) { // mutations.forEach(function (mutation) {
if (editorModifierTagsList.childNodes.length > 0) { if (editorModifierTagsList.childNodes.length > 0) {
ModifierToggle() ModifierToggle()
} }
// }) // })
}) })
observer.observe(editorModifierTagsList, { observer.observe(editorModifierTagsList, {
childList: true childList: true
}) })
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==