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

View File

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

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,8 @@ var ParameterType = {
select: "select",
select_multiple: "select_multiple",
slider: "slider",
custom: "custom",
};
custom: "custom"
}
/**
* JSDoc style
@ -24,7 +24,6 @@ var ParameterType = {
* @property {boolean?} saveInAppConfig
*/
/** @type {Array.<Parameter>} */
var PARAMETERS = [
{
@ -33,7 +32,8 @@ var PARAMETERS = [
label: "Theme",
default: "theme-default",
note: "customize the look and feel of the ui",
options: [ // Note: options expanded dynamically
options: [
// Note: options expanded dynamically
{
value: "theme-default",
label: "Default"
@ -47,7 +47,7 @@ var PARAMETERS = [
label: "Auto-Save Images",
note: "automatically saves images to the specified location",
icon: "fa-download",
default: false,
default: false
},
{
id: "diskPath",
@ -82,13 +82,13 @@ var PARAMETERS = [
},
{
value: "embed,txt",
label: "embed & txt",
label: "embed & txt"
},
{
value: "embed,json",
label: "embed & json",
},
],
label: "embed & json"
}
]
},
{
id: "block_nsfw",
@ -96,7 +96,7 @@ var PARAMETERS = [
label: "Block NSFW images",
note: "blurs out NSFW images",
icon: "fa-land-mine-on",
default: false,
default: false
},
{
id: "sound_toggle",
@ -104,7 +104,7 @@ var PARAMETERS = [
label: "Enable Sound",
note: "plays a sound on task completion",
icon: "fa-volume-low",
default: true,
default: true
},
{
id: "process_order_toggle",
@ -112,7 +112,7 @@ var PARAMETERS = [
label: "Process newest jobs first",
note: "reverse the normal processing order",
icon: "fa-arrow-down-short-wide",
default: false,
default: false
},
{
id: "ui_open_browser_on_start",
@ -121,23 +121,24 @@ var PARAMETERS = [
note: "starts the default browser on startup",
icon: "fa-window-restore",
default: true,
saveInAppConfig: true,
saveInAppConfig: true
},
{
id: "vram_usage_level",
type: ParameterType.select,
label: "GPU Memory Usage",
note: "Faster performance requires more GPU memory (VRAM)<br/><br/>" +
"<b>Balanced:</b> nearly as fast as High, much lower VRAM usage<br/>" +
"<b>High:</b> fastest, maximum GPU memory usage</br>" +
"<b>Low:</b> slowest, recommended for GPUs with 3 to 4 GB memory",
note:
"Faster performance requires more GPU memory (VRAM)<br/><br/>" +
"<b>Balanced:</b> nearly as fast as High, much lower VRAM usage<br/>" +
"<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",
default: "balanced",
options: [
{value: "balanced", label: "Balanced"},
{value: "high", label: "High"},
{value: "low", label: "Low"}
],
{ value: "balanced", label: "Balanced" },
{ value: "high", label: "High" },
{ value: "low", label: "Low" }
]
},
{
id: "use_cpu",
@ -145,20 +146,20 @@ var PARAMETERS = [
label: "Use CPU (not GPU)",
note: "warning: this will be *very* slow",
icon: "fa-microchip",
default: false,
default: false
},
{
id: "auto_pick_gpus",
type: ParameterType.checkbox,
label: "Automatically pick the GPUs (experimental)",
default: false,
default: false
},
{
id: "use_gpus",
type: ParameterType.select_multiple,
label: "GPUs to use (experimental)",
note: "to process in parallel",
default: false,
default: false
},
{
id: "auto_save_settings",
@ -166,15 +167,16 @@ var PARAMETERS = [
label: "Auto-Save Settings",
note: "restores settings on browser load",
icon: "fa-gear",
default: true,
default: true
},
{
id: "confirm_dangerous_actions",
type: ParameterType.checkbox,
label: "Confirm dangerous actions",
note: "Actions that might lead to data loss must either be clicked with the shift key pressed, or confirmed in an 'Are you sure?' dialog",
note:
"Actions that might lead to data loss must either be clicked with the shift key pressed, or confirmed in an 'Are you sure?' dialog",
icon: "fa-check-double",
default: true,
default: true
},
{
id: "listen_to_network",
@ -183,7 +185,7 @@ var PARAMETERS = [
note: "Other devices on your network can access this web page",
icon: "fa-network-wired",
default: true,
saveInAppConfig: true,
saveInAppConfig: true
},
{
id: "listen_port",
@ -194,29 +196,31 @@ var PARAMETERS = [
render: (parameter) => {
return `<input id="${parameter.id}" name="${parameter.id}" size="6" value="9000" onkeypress="preventNonNumericalInput(event)">`
},
saveInAppConfig: true,
saveInAppConfig: true
},
{
id: "use_beta_channel",
type: ParameterType.checkbox,
label: "Beta channel",
note: "Get the latest features immediately (but could be less stable). Please restart the program after changing this.",
note:
"Get the latest features immediately (but could be less stable). Please restart the program after changing this.",
icon: "fa-fire",
default: false,
default: false
},
{
id: "test_diffusers",
type: ParameterType.checkbox,
label: "Test Diffusers",
note: "<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",
default: false,
saveInAppConfig: true,
},
];
saveInAppConfig: true
}
]
function getParameterSettingsEntry(id) {
let parameter = PARAMETERS.filter(p => p.id === id)
let parameter = PARAMETERS.filter((p) => p.id === id)
if (parameter.length === 0) {
return
}
@ -224,37 +228,39 @@ function getParameterSettingsEntry(id) {
}
function sliderUpdate(event) {
if (event.srcElement.id.endsWith('-input')) {
let slider = document.getElementById(event.srcElement.id.slice(0,-6))
if (event.srcElement.id.endsWith("-input")) {
let slider = document.getElementById(event.srcElement.id.slice(0, -6))
slider.value = event.srcElement.value
slider.dispatchEvent(new Event("change"))
} else {
let field = document.getElementById(event.srcElement.id+'-input')
let field = document.getElementById(event.srcElement.id + "-input")
field.value = event.srcElement.value
field.dispatchEvent(new Event("change"))
}
}
/**
* @param {Parameter} parameter
* @param {Parameter} parameter
* @returns {string | HTMLElement}
*/
function getParameterElement(parameter) {
switch (parameter.type) {
case ParameterType.checkbox:
var is_checked = parameter.default ? " checked" : "";
var is_checked = parameter.default ? " checked" : ""
return `<input id="${parameter.id}" name="${parameter.id}"${is_checked} type="checkbox">`
case ParameterType.select:
case ParameterType.select_multiple:
var options = (parameter.options || []).map(option => `<option value="${option.value}">${option.label}</option>`).join("")
var multiple = (parameter.type == ParameterType.select_multiple ? 'multiple' : '')
var options = (parameter.options || [])
.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>`
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}`
case ParameterType.custom:
return parameter.render(parameter)
default:
console.error(`Invalid type ${parameter.type} for parameter ${parameter.id}`);
console.error(`Invalid type ${parameter.type} for parameter ${parameter.id}`)
return "ERROR: Invalid Type"
}
}
@ -265,31 +271,31 @@ let parametersTable = document.querySelector("#system-settings .parameters-table
* @param {Array<Parameter> | undefined} parameters
* */
function initParameters(parameters) {
parameters.forEach(parameter => {
parameters.forEach((parameter) => {
const element = getParameterElement(parameter)
const elementWrapper = createElement('div')
const elementWrapper = createElement("div")
if (element instanceof Node) {
elementWrapper.appendChild(element)
} else {
elementWrapper.innerHTML = element
}
const note = typeof parameter.note === 'function' ? parameter.note(parameter) : parameter.note
const note = typeof parameter.note === "function" ? parameter.note(parameter) : parameter.note
const noteElements = []
if (note) {
const noteElement = createElement('small')
const noteElement = createElement("small")
if (note instanceof Node) {
noteElement.appendChild(note)
} else {
noteElement.innerHTML = note || ''
noteElement.innerHTML = note || ""
}
noteElements.push(noteElement)
}
const icon = parameter.icon ? [createElement('i', undefined, ['fa', parameter.icon])] : []
const icon = parameter.icon ? [createElement("i", undefined, ["fa", parameter.icon])] : []
const label = typeof parameter.label === 'function' ? parameter.label(parameter) : parameter.label
const labelElement = createElement('label', { for: parameter.id })
const label = typeof parameter.label === "function" ? parameter.label(parameter) : parameter.label
const labelElement = createElement("label", { for: parameter.id })
if (label instanceof Node) {
labelElement.appendChild(label)
} else {
@ -297,13 +303,13 @@ function initParameters(parameters) {
}
const newrow = createElement(
'div',
{ 'data-setting-id': parameter.id, 'data-save-in-app-config': parameter.saveInAppConfig },
"div",
{ "data-setting-id": parameter.id, "data-save-in-app-config": parameter.saveInAppConfig },
undefined,
[
createElement('div', undefined, undefined, icon),
createElement('div', undefined, undefined, [labelElement, ...noteElements]),
elementWrapper,
createElement("div", undefined, undefined, icon),
createElement("div", undefined, undefined, [labelElement, ...noteElements]),
elementWrapper
]
)
parametersTable.appendChild(newrow)
@ -314,22 +320,25 @@ function initParameters(parameters) {
initParameters(PARAMETERS)
// listen to parameters from plugins
PARAMETERS.addEventListener('push', (...items) => {
PARAMETERS.addEventListener("push", (...items) => {
initParameters(items)
if (items.find(item => item.saveInAppConfig)) {
console.log('Reloading app config for new parameters', items.map(p => p.id))
if (items.find((item) => item.saveInAppConfig)) {
console.log(
"Reloading app config for new parameters",
items.map((p) => p.id)
)
getAppConfig()
}
})
let vramUsageLevelField = document.querySelector('#vram_usage_level')
let useCPUField = document.querySelector('#use_cpu')
let autoPickGPUsField = document.querySelector('#auto_pick_gpus')
let useGPUsField = document.querySelector('#use_gpus')
let saveToDiskField = document.querySelector('#save_to_disk')
let diskPathField = document.querySelector('#diskPath')
let metadataOutputFormatField = document.querySelector('#metadata_output_format')
let vramUsageLevelField = document.querySelector("#vram_usage_level")
let useCPUField = document.querySelector("#use_cpu")
let autoPickGPUsField = document.querySelector("#auto_pick_gpus")
let useGPUsField = document.querySelector("#use_gpus")
let saveToDiskField = document.querySelector("#save_to_disk")
let diskPathField = document.querySelector("#diskPath")
let metadataOutputFormatField = document.querySelector("#metadata_output_format")
let listenToNetworkField = document.querySelector("#listen_to_network")
let listenPortField = document.querySelector("#listen_port")
let useBetaChannelField = document.querySelector("#use_beta_channel")
@ -337,35 +346,34 @@ let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_star
let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions")
let testDiffusers = document.querySelector("#test_diffusers")
let saveSettingsBtn = document.querySelector('#save-system-settings-btn')
let saveSettingsBtn = document.querySelector("#save-system-settings-btn")
async function changeAppConfig(configDelta) {
try {
let res = await fetch('/app_config', {
method: 'POST',
let res = await fetch("/app_config", {
method: "POST",
headers: {
'Content-Type': 'application/json'
"Content-Type": "application/json"
},
body: JSON.stringify(configDelta)
})
res = await res.json()
console.log('set config status response', res)
console.log("set config status response", res)
} catch (e) {
console.log('set config status error', e)
console.log("set config status error", e)
}
}
async function getAppConfig() {
try {
let res = await fetch('/get/app_config')
let res = await fetch("/get/app_config")
const config = await res.json()
applySettingsFromConfig(config)
// custom overrides
if (config.update_branch === 'beta') {
if (config.update_branch === "beta") {
useBetaChannelField.checked = true
document.querySelector("#updateBranchLabel").innerText = "(beta)"
} else {
@ -380,45 +388,48 @@ async function getAppConfig() {
if (config.net && config.net.listen_port !== undefined) {
listenPortField.value = config.net.listen_port
}
if (config.test_diffusers === undefined || config.update_branch === 'main') {
if (config.test_diffusers === undefined || config.update_branch === "main") {
testDiffusers.checked = false
document.querySelector("#lora_model_container").style.display = 'none'
document.querySelector("#lora_alpha_container").style.display = 'none'
document.querySelector("#lora_model_container").style.display = "none"
document.querySelector("#lora_alpha_container").style.display = "none"
} else {
testDiffusers.checked = config.test_diffusers && config.update_branch !== 'main'
document.querySelector("#lora_model_container").style.display = (testDiffusers.checked ? '' : 'none')
document.querySelector("#lora_alpha_container").style.display = (testDiffusers.checked && loraModelField.value !== "" ? '' : 'none')
testDiffusers.checked = config.test_diffusers && config.update_branch !== "main"
document.querySelector("#lora_model_container").style.display = testDiffusers.checked ? "" : "none"
document.querySelector("#lora_alpha_container").style.display =
testDiffusers.checked && loraModelField.value !== "" ? "" : "none"
}
console.log('get config status response', config)
console.log("get config status response", config)
return config
} catch (e) {
console.log('get config status error', e)
console.log("get config status error", e)
return {}
}
}
function applySettingsFromConfig(config) {
Array.from(parametersTable.children).forEach(parameterRow => {
if (parameterRow.dataset.settingId in config && parameterRow.dataset.saveInAppConfig === 'true') {
Array.from(parametersTable.children).forEach((parameterRow) => {
if (parameterRow.dataset.settingId in config && parameterRow.dataset.saveInAppConfig === "true") {
const configValue = config[parameterRow.dataset.settingId]
const parameterElement = document.getElementById(parameterRow.dataset.settingId) ||
parameterRow.querySelector('input') || parameterRow.querySelector('select')
const parameterElement =
document.getElementById(parameterRow.dataset.settingId) ||
parameterRow.querySelector("input") ||
parameterRow.querySelector("select")
switch (parameterElement?.tagName) {
case 'INPUT':
if (parameterElement.type === 'checkbox') {
case "INPUT":
if (parameterElement.type === "checkbox") {
parameterElement.checked = configValue
} else {
parameterElement.value = configValue
}
parameterElement.dispatchEvent(new Event('change'))
parameterElement.dispatchEvent(new Event("change"))
break
case 'SELECT':
case "SELECT":
if (Array.isArray(configValue)) {
Array.from(parameterElement.options).forEach(option => {
Array.from(parameterElement.options).forEach((option) => {
if (configValue.includes(option.value || option.text)) {
option.selected = true
}
@ -426,82 +437,85 @@ function applySettingsFromConfig(config) {
} else {
parameterElement.value = configValue
}
parameterElement.dispatchEvent(new Event('change'))
parameterElement.dispatchEvent(new Event("change"))
break
}
}
})
}
saveToDiskField.addEventListener('change', function(e) {
saveToDiskField.addEventListener("change", function(e) {
diskPathField.disabled = !this.checked
metadataOutputFormatField.disabled = !this.checked
})
function getCurrentRenderDeviceSelection() {
let selectedGPUs = $('#use_gpus').val()
let selectedGPUs = $("#use_gpus").val()
if (useCPUField.checked && !autoPickGPUsField.checked) {
return 'cpu'
return "cpu"
}
if (autoPickGPUsField.checked || selectedGPUs.length == 0) {
return 'auto'
return "auto"
}
return selectedGPUs.join(',')
return selectedGPUs.join(",")
}
useCPUField.addEventListener('click', function() {
let gpuSettingEntry = getParameterSettingsEntry('use_gpus')
let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus')
useCPUField.addEventListener("click", function() {
let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
let autoPickGPUSettingEntry = getParameterSettingsEntry("auto_pick_gpus")
if (this.checked) {
gpuSettingEntry.style.display = 'none'
autoPickGPUSettingEntry.style.display = 'none'
autoPickGPUsField.setAttribute('data-old-value', autoPickGPUsField.checked)
gpuSettingEntry.style.display = "none"
autoPickGPUSettingEntry.style.display = "none"
autoPickGPUsField.setAttribute("data-old-value", autoPickGPUsField.checked)
autoPickGPUsField.checked = false
} else if (useGPUsField.options.length >= MIN_GPUS_TO_SHOW_SELECTION) {
gpuSettingEntry.style.display = ''
autoPickGPUSettingEntry.style.display = ''
let oldVal = autoPickGPUsField.getAttribute('data-old-value')
if (oldVal === null || oldVal === undefined) { // the UI started with CPU selected by default
gpuSettingEntry.style.display = ""
autoPickGPUSettingEntry.style.display = ""
let oldVal = autoPickGPUsField.getAttribute("data-old-value")
if (oldVal === null || oldVal === undefined) {
// the UI started with CPU selected by default
autoPickGPUsField.checked = true
} else {
autoPickGPUsField.checked = (oldVal === 'true')
autoPickGPUsField.checked = oldVal === "true"
}
gpuSettingEntry.style.display = (autoPickGPUsField.checked ? 'none' : '')
gpuSettingEntry.style.display = autoPickGPUsField.checked ? "none" : ""
}
})
useGPUsField.addEventListener('click', function() {
let selectedGPUs = $('#use_gpus').val()
autoPickGPUsField.checked = (selectedGPUs.length === 0)
useGPUsField.addEventListener("click", function() {
let selectedGPUs = $("#use_gpus").val()
autoPickGPUsField.checked = selectedGPUs.length === 0
})
autoPickGPUsField.addEventListener('click', function() {
autoPickGPUsField.addEventListener("click", function() {
if (this.checked) {
$('#use_gpus').val([])
$("#use_gpus").val([])
}
let gpuSettingEntry = getParameterSettingsEntry('use_gpus')
gpuSettingEntry.style.display = (this.checked ? 'none' : '')
let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
gpuSettingEntry.style.display = this.checked ? "none" : ""
})
async function setDiskPath(defaultDiskPath, force=false) {
async function setDiskPath(defaultDiskPath, force = false) {
var diskPath = getSetting("diskPath")
if (force || diskPath == '' || diskPath == undefined || diskPath == "undefined") {
if (force || diskPath == "" || diskPath == undefined || diskPath == "undefined") {
setSetting("diskPath", defaultDiskPath)
}
}
function setDeviceInfo(devices) {
let cpu = devices.all.cpu.name
let allGPUs = Object.keys(devices.all).filter(d => d != 'cpu')
let allGPUs = Object.keys(devices.all).filter((d) => d != "cpu")
let activeGPUs = Object.keys(devices.active)
function ID_TO_TEXT(d) {
let info = devices.all[d]
if ("mem_free" in info && "mem_total" in info) {
return `${info.name} <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 {
return `${info.name} <small>(${d}) (no memory info)</small>`
}
@ -510,35 +524,35 @@ function setDeviceInfo(devices) {
allGPUs = allGPUs.map(ID_TO_TEXT)
activeGPUs = activeGPUs.map(ID_TO_TEXT)
let systemInfoEl = document.querySelector('#system-info')
systemInfoEl.querySelector('#system-info-cpu').innerText = cpu
systemInfoEl.querySelector('#system-info-gpus-all').innerHTML = allGPUs.join('</br>')
systemInfoEl.querySelector('#system-info-rendering-devices').innerHTML = activeGPUs.join('</br>')
let systemInfoEl = document.querySelector("#system-info")
systemInfoEl.querySelector("#system-info-cpu").innerText = cpu
systemInfoEl.querySelector("#system-info-gpus-all").innerHTML = allGPUs.join("</br>")
systemInfoEl.querySelector("#system-info-rendering-devices").innerHTML = activeGPUs.join("</br>")
}
function setHostInfo(hosts) {
let port = listenPortField.value
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('')
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("")
}
async function getSystemInfo() {
try {
const res = await SD.getSystemInfo()
let devices = res['devices']
let devices = res["devices"]
let allDeviceIds = Object.keys(devices['all']).filter(d => d !== 'cpu')
let activeDeviceIds = Object.keys(devices['active']).filter(d => d !== 'cpu')
let allDeviceIds = Object.keys(devices["all"]).filter((d) => d !== "cpu")
let activeDeviceIds = Object.keys(devices["active"]).filter((d) => d !== "cpu")
if (activeDeviceIds.length === 0) {
useCPUField.checked = true
}
if (allDeviceIds.length < MIN_GPUS_TO_SHOW_SELECTION || useCPUField.checked) {
let gpuSettingEntry = getParameterSettingsEntry('use_gpus')
gpuSettingEntry.style.display = 'none'
let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus')
autoPickGPUSettingEntry.style.display = 'none'
let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
gpuSettingEntry.style.display = "none"
let autoPickGPUSettingEntry = getParameterSettingsEntry("auto_pick_gpus")
autoPickGPUSettingEntry.style.display = "none"
}
if (allDeviceIds.length === 0) {
@ -546,86 +560,90 @@ async function getSystemInfo() {
useCPUField.disabled = true // no compatible GPUs, so make the CPU mandatory
}
autoPickGPUsField.checked = (devices['config'] === 'auto')
autoPickGPUsField.checked = devices["config"] === "auto"
useGPUsField.innerHTML = ''
allDeviceIds.forEach(device => {
let deviceName = devices['all'][device]['name']
useGPUsField.innerHTML = ""
allDeviceIds.forEach((device) => {
let deviceName = devices["all"][device]["name"]
let deviceOption = `<option value="${device}">${deviceName} (${device})</option>`
useGPUsField.insertAdjacentHTML('beforeend', deviceOption)
useGPUsField.insertAdjacentHTML("beforeend", deviceOption)
})
if (autoPickGPUsField.checked) {
let gpuSettingEntry = getParameterSettingsEntry('use_gpus')
gpuSettingEntry.style.display = 'none'
let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
gpuSettingEntry.style.display = "none"
} else {
$('#use_gpus').val(activeDeviceIds)
$("#use_gpus").val(activeDeviceIds)
}
setDeviceInfo(devices)
setHostInfo(res['hosts'])
setHostInfo(res["hosts"])
let force = false
if (res['enforce_output_dir'] !== undefined) {
force = res['enforce_output_dir']
if (res["enforce_output_dir"] !== undefined) {
force = res["enforce_output_dir"]
if (force == true) {
saveToDiskField.checked = true
metadataOutputFormatField.disabled = false
saveToDiskField.checked = true
metadataOutputFormatField.disabled = false
}
saveToDiskField.disabled = force
diskPathField.disabled = force
}
setDiskPath(res['default_output_dir'], force)
setDiskPath(res["default_output_dir"], force)
} catch (e) {
console.log('error fetching devices', e)
console.log("error fetching devices", e)
}
}
saveSettingsBtn.addEventListener('click', function() {
if (listenPortField.value == '') {
alert('The network port field must not be empty.')
saveSettingsBtn.addEventListener("click", function() {
if (listenPortField.value == "") {
alert("The network port field must not be empty.")
return
}
if (listenPortField.value < 1 || listenPortField.value > 65535) {
alert('The network port must be a number from 1 to 65535')
alert("The network port must be a number from 1 to 65535")
return
}
const updateBranch = (useBetaChannelField.checked ? 'beta' : 'main')
const updateBranch = useBetaChannelField.checked ? "beta" : "main"
const updateAppConfigRequest = {
'render_devices': getCurrentRenderDeviceSelection(),
'update_branch': updateBranch,
render_devices: getCurrentRenderDeviceSelection(),
update_branch: updateBranch
}
Array.from(parametersTable.children).forEach(parameterRow => {
if (parameterRow.dataset.saveInAppConfig === 'true') {
const parameterElement = document.getElementById(parameterRow.dataset.settingId) ||
parameterRow.querySelector('input') || parameterRow.querySelector('select')
Array.from(parametersTable.children).forEach((parameterRow) => {
if (parameterRow.dataset.saveInAppConfig === "true") {
const parameterElement =
document.getElementById(parameterRow.dataset.settingId) ||
parameterRow.querySelector("input") ||
parameterRow.querySelector("select")
switch (parameterElement?.tagName) {
case 'INPUT':
if (parameterElement.type === 'checkbox') {
case "INPUT":
if (parameterElement.type === "checkbox") {
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.checked
} else {
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value
}
break
case 'SELECT':
case "SELECT":
if (parameterElement.multiple) {
updateAppConfigRequest[parameterRow.dataset.settingId] = Array.from(parameterElement.options)
.filter(option => option.selected)
.map(option => option.value || option.text)
.filter((option) => option.selected)
.map((option) => option.value || option.text)
} else {
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value
}
break
default:
console.error(`Setting parameter ${parameterRow.dataset.settingId} couldn't be saved to app.config - element #${parameter.id} is a <${parameterElement?.tagName} /> instead of a <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
}
}
})
const savePromise = changeAppConfig(updateAppConfigRequest)
saveSettingsBtn.classList.add('active')
Promise.all([savePromise, asyncDelay(300)]).then(() => saveSettingsBtn.classList.remove('active'))
saveSettingsBtn.classList.add("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 = {
/**
* Register new buttons to show on each output image.
*
*
* Example:
* PLUGINS['IMAGE_INFO_BUTTONS'].push({
* text: 'Make a Similar Image',
@ -29,14 +29,20 @@ const PLUGINS = {
MODIFIERS_LOAD: [],
TASK_CREATE: [],
OUTPUTS_FORMATS: new ServiceContainer(
function png() { return (reqBody) => new SD.RenderTask(reqBody) }
, function jpeg() { return (reqBody) => new SD.RenderTask(reqBody) }
, function webp() { return (reqBody) => new SD.RenderTask(reqBody) }
),
function png() {
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) {
const service = ServiceContainer.prototype.register.apply(this, args)
if (typeof outputFormatField !== 'undefined') {
if (typeof outputFormatField !== "undefined") {
const newOption = document.createElement("option")
newOption.setAttribute("value", service.name)
newOption.innerText = service.name
@ -46,13 +52,13 @@ PLUGINS.OUTPUTS_FORMATS.register = function(...args) {
}
function loadScript(url) {
const script = document.createElement('script')
const script = document.createElement("script")
const promiseSrc = new PromiseSource()
script.addEventListener('error', () => promiseSrc.reject(new Error(`Script "${url}" couldn't be loaded.`)))
script.addEventListener('load', () => promiseSrc.resolve(url))
script.src = url + '?t=' + Date.now()
script.addEventListener("error", () => promiseSrc.reject(new Error(`Script "${url}" couldn't be loaded.`)))
script.addEventListener("load", () => promiseSrc.resolve(url))
script.src = url + "?t=" + Date.now()
console.log('loading script', url)
console.log("loading script", url)
document.head.appendChild(script)
return promiseSrc.promise
@ -60,7 +66,7 @@ function loadScript(url) {
async function loadUIPlugins() {
try {
const res = await fetch('/get/ui_plugins')
const res = await fetch("/get/ui_plugins")
if (!res.ok) {
console.error(`Error HTTP${res.status} while loading plugins list. - ${res.statusText}`)
return
@ -69,6 +75,6 @@ async function loadUIPlugins() {
const loadingPromises = plugins.map(loadScript)
return await Promise.allSettled(loadingPromises)
} 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.
*/
class ModelDropdown
{
class ModelDropdown {
modelFilter //= document.querySelector("#model-filter")
modelFilterArrow //= document.querySelector("#model-filter-arrow")
modelList //= document.querySelector("#model-list")
modelResult //= document.querySelector("#model-result")
modelNoResult //= document.querySelector("#model-no-result")
currentSelection //= { elem: undefined, value: '', path: ''}
highlightedModelEntry //= undefined
activeModel //= undefined
@ -59,11 +58,11 @@ class ModelDropdown
set disabled(state) {
this.modelFilter.disabled = state
if (this.modelFilterArrow) {
this.modelFilterArrow.style.color = state ? 'dimgray' : ''
this.modelFilterArrow.style.color = state ? "dimgray" : ""
}
}
get modelElements() {
return this.modelList.querySelectorAll('.model-file')
return this.modelList.querySelectorAll(".model-file")
}
addEventListener(type, listener, options) {
return this.modelFilter.addEventListener(type, listener, options)
@ -82,21 +81,26 @@ class ModelDropdown
}
}
/* SEARCHABLE INPUT */
constructor (input, modelKey, noneEntry = '') {
/* SEARCHABLE INPUT */
constructor(input, modelKey, noneEntry = "") {
this.modelFilter = input
this.noneEntry = noneEntry
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.populateModels()
}
document.addEventListener("refreshModels", this.bind(function(e) {
// reload the models
this.inputModels = modelsOptions[this.modelKey]
this.populateModels()
}, this))
document.addEventListener(
"refreshModels",
this.bind(function(e) {
// reload the models
this.inputModels = modelsOptions[this.modelKey]
this.populateModels()
}, this)
)
}
saveCurrentSelection(elem, value, path) {
@ -105,13 +109,13 @@ class ModelDropdown
this.currentSelection.path = path
this.modelFilter.dataset.path = path
this.modelFilter.value = value
this.modelFilter.dispatchEvent(new Event('change'))
this.modelFilter.dispatchEvent(new Event("change"))
}
processClick(e) {
e.preventDefault()
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
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
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
this.hideModelList()
this.modelFilter.focus()
@ -126,66 +130,67 @@ class ModelDropdown
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) {
let lastElementChild = elem.lastElementChild
if (lastElementChild.style.display == 'list-item') return lastElementChild
if (lastElementChild.style.display == "list-item") return lastElementChild
return this.getPreviousVisibleSibling(lastElementChild)
}
getNextVisibleSibling(elem) {
const modelElements = Array.from(this.modelElements)
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) {
let firstElementChild = elem.firstElementChild
if (firstElementChild.style.display == 'list-item') return firstElementChild
if (firstElementChild.style.display == "list-item") return firstElementChild
return this.getNextVisibleSibling(firstElementChild)
}
selectModelEntry(elem) {
if (elem) {
if (this.highlightedModelEntry !== undefined) {
this.highlightedModelEntry.classList.remove('selected')
this.highlightedModelEntry.classList.remove("selected")
}
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
elem.classList.add('selected')
elem.scrollIntoView({block: 'nearest'})
elem.classList.add("selected")
elem.scrollIntoView({ block: "nearest" })
this.highlightedModelEntry = elem
}
}
selectPreviousFile() {
const elem = this.getPreviousVisibleSibling(this.highlightedModelEntry)
if (elem) {
this.selectModelEntry(elem)
}
else
{
} else {
//this.highlightedModelEntry.parentElement.parentElement.scrollIntoView({block: 'nearest'})
this.highlightedModelEntry.closest('.model-list').scrollTop = 0
this.highlightedModelEntry.closest(".model-list").scrollTop = 0
}
this.modelFilter.select()
}
selectNextFile() {
this.selectModelEntry(this.getNextVisibleSibling(this.highlightedModelEntry))
this.modelFilter.select()
}
selectFirstFile() {
this.selectModelEntry(this.modelList.querySelector('.model-file'))
this.highlightedModelEntry.scrollIntoView({block: 'nearest'})
this.selectModelEntry(this.modelList.querySelector(".model-file"))
this.highlightedModelEntry.scrollIntoView({ block: "nearest" })
this.modelFilter.select()
}
selectLastFile() {
const elems = this.modelList.querySelectorAll('.model-file:last-child')
this.selectModelEntry(elems[elems.length -1])
const elems = this.modelList.querySelectorAll(".model-file:last-child")
this.selectModelEntry(elems[elems.length - 1])
this.modelFilter.select()
}
@ -198,57 +203,57 @@ class ModelDropdown
}
validEntrySelected() {
return (this.modelNoResult.style.display === 'none')
return this.modelNoResult.style.display === "none"
}
processKey(e) {
switch (e.key) {
case 'Escape':
case "Escape":
e.preventDefault()
this.resetSelection()
break
case 'Enter':
case "Enter":
e.preventDefault()
if (this.validEntrySelected()) {
if (this.modelList.style.display != 'block') {
if (this.modelList.style.display != "block") {
this.showModelList()
}
else
{
this.saveCurrentSelection(this.highlightedModelEntry, this.highlightedModelEntry.innerText, this.highlightedModelEntry.dataset.path)
} else {
this.saveCurrentSelection(
this.highlightedModelEntry,
this.highlightedModelEntry.innerText,
this.highlightedModelEntry.dataset.path
)
this.hideModelList()
this.showAllEntries()
}
this.modelFilter.focus()
}
else
{
} else {
this.resetSelection()
}
break
case 'ArrowUp':
case "ArrowUp":
e.preventDefault()
if (this.validEntrySelected()) {
this.selectPreviousFile()
}
break
case 'ArrowDown':
case "ArrowDown":
e.preventDefault()
if (this.validEntrySelected()) {
this.selectNextFile()
}
break
case 'ArrowLeft':
if (this.modelList.style.display != 'block') {
case "ArrowLeft":
if (this.modelList.style.display != "block") {
e.preventDefault()
}
break
case 'ArrowRight':
if (this.modelList.style.display != 'block') {
case "ArrowRight":
if (this.modelList.style.display != "block") {
e.preventDefault()
}
break
case 'PageUp':
case "PageUp":
e.preventDefault()
if (this.validEntrySelected()) {
this.selectPreviousFile()
@ -261,7 +266,7 @@ class ModelDropdown
this.selectPreviousFile()
}
break
case 'PageDown':
case "PageDown":
e.preventDefault()
if (this.validEntrySelected()) {
this.selectNextFile()
@ -274,201 +279,195 @@ class ModelDropdown
this.selectNextFile()
}
break
case 'Home':
case "Home":
//if (this.modelList.style.display != 'block') {
e.preventDefault()
if (this.validEntrySelected()) {
this.selectFirstFile()
}
e.preventDefault()
if (this.validEntrySelected()) {
this.selectFirstFile()
}
//}
break
case 'End':
case "End":
//if (this.modelList.style.display != 'block') {
e.preventDefault()
if (this.validEntrySelected()) {
this.selectLastFile()
}
e.preventDefault()
if (this.validEntrySelected()) {
this.selectLastFile()
}
//}
break
default:
//console.log(e.key)
//console.log(e.key)
}
}
modelListFocus() {
this.selectEntry()
this.showAllEntries()
}
showModelList() {
this.modelList.style.display = 'block'
this.modelList.style.display = "block"
this.selectEntry()
this.showAllEntries()
//this.modelFilter.value = ''
this.modelFilter.select() // preselect the entire string so user can just start typing.
this.modelFilter.focus()
this.modelFilter.style.cursor = 'auto'
this.modelFilter.style.cursor = "auto"
}
hideModelList() {
this.modelList.style.display = 'none'
this.modelList.style.display = "none"
this.modelFilter.value = this.currentSelection.value
this.modelFilter.style.cursor = ''
this.modelFilter.style.cursor = ""
}
toggleModelList(e) {
e.preventDefault()
if (!this.modelFilter.disabled) {
if (this.modelList.style.display != 'block') {
if (this.modelList.style.display != "block") {
this.showModelList()
}
else
{
} else {
this.hideModelList()
this.modelFilter.select()
}
}
}
selectEntry(path) {
if (path !== undefined) {
const entries = this.modelElements;
const entries = this.modelElements
for (const elem of entries) {
if (elem.dataset.path == path) {
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
this.highlightedModelEntry = elem
elem.scrollIntoView({block: 'nearest'})
elem.scrollIntoView({ block: "nearest" })
break
}
}
}
if (this.currentSelection.elem !== undefined) {
// select the previous element
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.currentSelection.elem.scrollIntoView({block: 'nearest'})
}
else
{
this.currentSelection.elem.scrollIntoView({ block: "nearest" })
} else {
this.selectFirstFile()
}
}
highlightModelAtPosition(e) {
let elem = document.elementFromPoint(e.clientX, e.clientY)
if (elem.classList.contains('model-file')) {
if (elem.classList.contains("model-file")) {
this.highlightModel(elem)
}
}
highlightModel(elem) {
if (elem.classList.contains('model-file')) {
if (elem.classList.contains("model-file")) {
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
}
}
showAllEntries() {
this.modelList.querySelectorAll('li').forEach(function(li) {
if (li.id !== 'model-no-result') {
li.style.display = 'list-item'
this.modelList.querySelectorAll("li").forEach(function(li) {
if (li.id !== "model-no-result") {
li.style.display = "list-item"
}
})
this.modelNoResult.style.display = 'none'
this.modelNoResult.style.display = "none"
}
filterList(e) {
const filter = this.modelFilter.value.toLowerCase()
let found = false
let showAllChildren = false
this.modelList.querySelectorAll('li').forEach(function(li) {
if (li.classList.contains('model-folder')) {
this.modelList.querySelectorAll("li").forEach(function(li) {
if (li.classList.contains("model-folder")) {
showAllChildren = false
}
if (filter == '') {
li.style.display = 'list-item'
if (filter == "") {
li.style.display = "list-item"
found = true
} else if (showAllChildren || li.textContent.toLowerCase().match(filter)) {
li.style.display = 'list-item'
if (li.classList.contains('model-folder') && li.firstChild.textContent.toLowerCase().match(filter)) {
li.style.display = "list-item"
if (li.classList.contains("model-folder") && li.firstChild.textContent.toLowerCase().match(filter)) {
showAllChildren = true
}
found = true
} else {
li.style.display = 'none'
li.style.display = "none"
}
})
if (found) {
this.modelResult.style.display = 'list-item'
this.modelNoResult.style.display = 'none'
const elem = this.getNextVisibleSibling(this.modelList.querySelector('.model-file'))
this.modelResult.style.display = "list-item"
this.modelNoResult.style.display = "none"
const elem = this.getNextVisibleSibling(this.modelList.querySelector(".model-file"))
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.modelResult.style.display = 'none'
this.modelNoResult.style.display = 'list-item'
}
this.modelList.style.display = 'block'
this.modelList.style.display = "block"
}
/* MODEL LOADER */
getElementDimensions(element) {
// Clone the element
const clone = element.cloneNode(true)
// Copy the styles of the original element to the cloned element
const originalStyles = window.getComputedStyle(element)
for (let i = 0; i < originalStyles.length; i++) {
const property = originalStyles[i]
clone.style[property] = originalStyles.getPropertyValue(property)
}
// Set its visibility to hidden and display to inline-block
clone.style.visibility = "hidden"
clone.style.display = "inline-block"
// Put the cloned element next to the original element
element.parentNode.insertBefore(clone, element.nextSibling)
// Get its width and height
const width = clone.offsetWidth
const height = clone.offsetHeight
// Remove it from the DOM
clone.remove()
// Return its width and height
return { width, height }
}
/**
* @param {Array<string>} models
* @param {Array<string>} models
*/
sortStringArray(models) {
models.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
models.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
}
populateModels() {
this.activeModel = this.modelFilter.dataset.path
this.currentSelection = { elem: undefined, value: '', path: ''}
this.currentSelection = { elem: undefined, value: "", path: "" }
this.highlightedModelEntry = undefined
this.flatModelList = []
if(this.modelList !== undefined) {
if (this.modelList !== undefined) {
this.modelList.remove()
this.modelFilterArrow.remove()
}
@ -478,39 +477,39 @@ class ModelDropdown
createDropdown() {
// create dropdown entries
let rootModelList = this.createRootModelList(this.inputModels)
this.modelFilter.insertAdjacentElement('afterend', rootModelList)
this.modelFilter.insertAdjacentElement("afterend", rootModelList)
this.modelFilter.insertAdjacentElement(
'afterend',
createElement(
'i',
{ id: `${this.modelFilter.id}-model-filter-arrow` },
['model-selector-arrow', 'fa-solid', 'fa-angle-down'],
),
"afterend",
createElement("i", { id: `${this.modelFilter.id}-model-filter-arrow` }, [
"model-selector-arrow",
"fa-solid",
"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`)
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.modelResult = document.querySelector(`#${this.modelFilter.id}-model-result`)
this.modelNoResult = document.querySelector(`#${this.modelFilter.id}-model-no-result`)
if (this.modelFilterInitialized !== true) {
this.modelFilter.addEventListener('input', this.bind(this.filterList, this))
this.modelFilter.addEventListener('focus', this.bind(this.modelListFocus, this))
this.modelFilter.addEventListener('blur', this.bind(this.hideModelList, this))
this.modelFilter.addEventListener('click', this.bind(this.showModelList, this))
this.modelFilter.addEventListener('keydown', this.bind(this.processKey, this))
this.modelFilter.addEventListener("input", this.bind(this.filterList, this))
this.modelFilter.addEventListener("focus", this.bind(this.modelListFocus, this))
this.modelFilter.addEventListener("blur", this.bind(this.hideModelList, this))
this.modelFilter.addEventListener("click", this.bind(this.showModelList, this))
this.modelFilter.addEventListener("keydown", this.bind(this.processKey, this))
this.modelFilterInitialized = true
}
this.modelFilterArrow.addEventListener('mousedown', this.bind(this.toggleModelList, this))
this.modelList.addEventListener('mousemove', this.bind(this.highlightModelAtPosition, this))
this.modelList.addEventListener('mousedown', this.bind(this.processClick, this))
this.modelFilterArrow.addEventListener("mousedown", this.bind(this.toggleModelList, this))
this.modelList.addEventListener("mousemove", this.bind(this.highlightModelAtPosition, this))
this.modelList.addEventListener("mousedown", this.bind(this.processClick, this))
let mf = this.modelFilter
this.modelFilter.addEventListener('focus', function() {
this.modelFilter.addEventListener("focus", function() {
let modelFilterStyle = window.getComputedStyle(mf)
rootModelList.style.minWidth = modelFilterStyle.width
})
@ -520,74 +519,62 @@ class ModelDropdown
/**
* @param {Array<string | object} modelTree
* @param {string} folderName
* @param {boolean} isRootFolder
* @param {string} folderName
* @param {boolean} isRootFolder
* @returns {HTMLElement}
*/
createModelNodeList(folderName, modelTree, isRootFolder) {
const listElement = createElement('ul')
const listElement = createElement("ul")
const foldersMap = new Map()
const modelsMap = new Map()
modelTree.forEach(model => {
modelTree.forEach((model) => {
if (Array.isArray(model)) {
const [childFolderName, childModels] = model
foldersMap.set(
childFolderName,
this.createModelNodeList(
`${folderName || ''}/${childFolderName}`,
childModels,
false,
),
this.createModelNodeList(`${folderName || ""}/${childFolderName}`, childModels, false)
)
} else {
const classes = ['model-file']
const classes = ["model-file"]
if (isRootFolder) {
classes.push('in-root-folder')
classes.push("in-root-folder")
}
// Remove the leading slash from the model path
const fullPath = folderName ? `${folderName.substring(1)}/${model}` : model
modelsMap.set(
model,
createElement(
'li',
{ 'data-path': fullPath },
classes,
[
createElement('i', undefined, ['fa-regular', 'fa-file', 'icon']),
model,
],
),
createElement("li", { "data-path": fullPath }, classes, [
createElement("i", undefined, ["fa-regular", "fa-file", "icon"]),
model
])
)
}
})
const childFolderNames = Array.from(foldersMap.keys())
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())
this.sortStringArray(modelNames)
const modelElements = modelNames.map(name => modelsMap.get(name))
const modelElements = modelNames.map((name) => modelsMap.get(name))
if (modelElements.length && folderName) {
listElement.appendChild(
createElement(
'li',
"li",
undefined,
['model-folder'],
[
createElement('i', undefined, ['fa-regular', 'fa-folder-open', 'icon']),
folderName.substring(1),
],
["model-folder"],
[createElement("i", undefined, ["fa-regular", "fa-folder-open", "icon"]), folderName.substring(1)]
)
)
}
// const allModelElements = isRootFolder ? [...folderElements, ...modelElements] : [...modelElements, ...folderElements]
const allModelElements = [...modelElements, ...folderElements]
allModelElements.forEach(e => listElement.appendChild(e))
allModelElements.forEach((e) => listElement.appendChild(e))
return listElement
}
@ -596,37 +583,21 @@ class ModelDropdown
* @returns {HTMLElement}
*/
createRootModelList(modelTree) {
const rootList = createElement(
'ul',
{ id: `${this.modelFilter.id}-model-list` },
['model-list'],
)
const rootList = createElement("ul", { id: `${this.modelFilter.id}-model-list` }, ["model-list"])
rootList.appendChild(
createElement(
'li',
{ id: `${this.modelFilter.id}-model-no-result` },
['model-no-result'],
'No result'
),
createElement("li", { id: `${this.modelFilter.id}-model-no-result` }, ["model-no-result"], "No result")
)
if (this.noneEntry) {
rootList.appendChild(
createElement(
'li',
{ 'data-path': '' },
['model-file', 'in-root-folder'],
this.noneEntry,
),
createElement("li", { "data-path": "" }, ["model-file", "in-root-folder"], this.noneEntry)
)
}
if (modelTree.length > 0) {
const containerListItem = createElement(
'li',
{ id: `${this.modelFilter.id}-model-result` },
['model-result'],
)
const containerListItem = createElement("li", { id: `${this.modelFilter.id}-model-result` }, [
"model-result"
])
//console.log(containerListItem)
containerListItem.appendChild(this.createModelNodeList(undefined, modelTree, true))
rootList.appendChild(containerListItem)
@ -640,13 +611,16 @@ class ModelDropdown
async function getModels() {
try {
modelsCache = await SD.getModels()
modelsOptions = modelsCache['options']
modelsOptions = modelsCache["options"]
if ("scan-error" in modelsCache) {
// let previewPane = document.getElementById('tab-content-wrapper')
let previewPane = document.getElementById('preview')
previewPane.style.background="red"
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>'
let previewPane = document.getElementById("preview")
previewPane.style.background = "red"
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>'
makeImageBtn.disabled = true
}
@ -667,11 +641,11 @@ async function getModels() {
*/
// notify ModelDropdown objects to refresh
document.dispatchEvent(new Event('refreshModels'))
document.dispatchEvent(new Event("refreshModels"))
} catch (e) {
console.log('get models error', e)
console.log("get models error", e)
}
}
// 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");
var DEFAULT_THEME = {};
var THEMES = []; // initialized in initTheme from data in css
const themeField = document.getElementById("theme")
var DEFAULT_THEME = {}
var THEMES = [] // initialized in initTheme from data in css
function getThemeName(theme) {
theme = theme.replace("theme-", "");
theme = theme.split("-").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
return theme;
theme = theme.replace("theme-", "")
theme = theme
.split("-")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ")
return theme
}
// init themefield
function initTheme() {
Array.from(document.styleSheets)
.filter(sheet => sheet.href?.startsWith(window.location.origin))
.flatMap(sheet => Array.from(sheet.cssRules))
.forEach(rule => {
var selector = rule.selectorText;
.filter((sheet) => sheet.href?.startsWith(window.location.origin))
.flatMap((sheet) => Array.from(sheet.cssRules))
.forEach((rule) => {
var selector = rule.selectorText
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)
.filter(cssVariable => !Array.from(rule.style).includes(cssVariable))
.forEach(cssVariable => {
rule.style.setProperty(cssVariable, DEFAULT_THEME.rule.style.getPropertyValue(cssVariable));
});
.filter((cssVariable) => !Array.from(rule.style).includes(cssVariable))
.forEach((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({
key: theme_key,
name: getThemeName(theme_key),
@ -34,49 +38,48 @@ function initTheme() {
key: "theme-default",
name: "Default",
rule: rule
};
}
}
});
THEMES.forEach(theme => {
var new_option = document.createElement("option");
new_option.setAttribute("value", theme.key);
new_option.innerText = theme.name;
themeField.appendChild(new_option);
});
})
THEMES.forEach((theme) => {
var new_option = document.createElement("option")
new_option.setAttribute("value", theme.key)
new_option.innerText = theme.name
themeField.appendChild(new_option)
})
// setup the style transitions a second after app initializes, so initial style is instant
setTimeout(() => {
var body = document.querySelector("body");
var style = document.createElement('style');
style.innerHTML = "* { transition: background 0.5s, color 0.5s, background-color 0.5s; }";
body.appendChild(style);
}, 1000);
var body = document.querySelector("body")
var style = document.createElement("style")
style.innerHTML = "* { transition: background 0.5s, color 0.5s, background-color 0.5s; }"
body.appendChild(style)
}, 1000)
}
initTheme();
initTheme()
function themeFieldChanged() {
var theme_key = themeField.value;
var theme_key = themeField.value
var body = document.querySelector("body");
body.classList.remove(...THEMES.map(theme => theme.key));
body.classList.add(theme_key);
//
var body = document.querySelector("body")
body.classList.remove(...THEMES.map((theme) => 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
if (theme) {
borderColor = theme.rule.style.getPropertyValue('--input-border-color').trim()
if (!borderColor.startsWith('#')) {
borderColor = theme.rule.style.getPropertyValue('--theme-color-fallback')
borderColor = theme.rule.style.getPropertyValue("--input-border-color").trim()
if (!borderColor.startsWith("#")) {
borderColor = theme.rule.style.getPropertyValue("--theme-color-fallback")
}
} 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)
}
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/
function getNextSibling(elem, selector) {
@ -20,33 +20,34 @@ function getNextSibling(elem, selector) {
}
}
/* Panel Stuff */
// true = open
let COLLAPSIBLES_INITIALIZED = false;
const COLLAPSIBLES_KEY = "collapsibles";
const COLLAPSIBLE_PANELS = []; // filled in by createCollapsibles with all the elements matching .collapsible
let COLLAPSIBLES_INITIALIZED = false
const COLLAPSIBLES_KEY = "collapsibles"
const COLLAPSIBLE_PANELS = [] // filled in by createCollapsibles with all the elements matching .collapsible
// on-init call this for any panels that are marked open
function toggleCollapsible(element) {
const collapsibleHeader = element.querySelector(".collapsible");
const handle = element.querySelector(".collapsible-handle");
const collapsibleHeader = element.querySelector(".collapsible")
const handle = element.querySelector(".collapsible-handle")
collapsibleHeader.classList.toggle("active")
let content = getNextSibling(collapsibleHeader, '.collapsible-content')
let content = getNextSibling(collapsibleHeader, ".collapsible-content")
if (!collapsibleHeader.classList.contains("active")) {
content.style.display = "none"
if (handle != null) { // render results don't have a handle
handle.innerHTML = '&#x2795;' // plus
if (handle != null) {
// render results don't have a handle
handle.innerHTML = "&#x2795;" // plus
}
} else {
content.style.display = "block"
if (handle != null) { // render results don't have a handle
handle.innerHTML = '&#x2796;' // minus
if (handle != null) {
// 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)) {
saveCollapsibles()
}
@ -54,7 +55,7 @@ function toggleCollapsible(element) {
function saveCollapsibles() {
let values = {}
COLLAPSIBLE_PANELS.forEach(element => {
COLLAPSIBLE_PANELS.forEach((element) => {
let value = element.querySelector(".collapsible").className.indexOf("active") !== -1
values[element.id] = value
})
@ -72,31 +73,31 @@ function createCollapsibles(node) {
if (save && c.parentElement.id) {
COLLAPSIBLE_PANELS.push(c.parentElement)
}
let handle = document.createElement('span')
handle.className = 'collapsible-handle'
let handle = document.createElement("span")
handle.className = "collapsible-handle"
if (c.classList.contains("active")) {
handle.innerHTML = '&#x2796;' // minus
handle.innerHTML = "&#x2796;" // minus
} else {
handle.innerHTML = '&#x2795;' // plus
handle.innerHTML = "&#x2795;" // plus
}
c.insertBefore(handle, c.firstChild)
c.addEventListener('click', function() {
c.addEventListener("click", function() {
toggleCollapsible(c.parentElement)
})
})
if (save) {
let saved = localStorage.getItem(COLLAPSIBLES_KEY)
if (!saved) {
saved = tryLoadOldCollapsibles();
if (!saved) {
saved = tryLoadOldCollapsibles()
}
if (!saved) {
saveCollapsibles()
saved = localStorage.getItem(COLLAPSIBLES_KEY)
}
let values = JSON.parse(saved)
COLLAPSIBLE_PANELS.forEach(element => {
COLLAPSIBLE_PANELS.forEach((element) => {
let value = element.querySelector(".collapsible").className.indexOf("active") !== -1
if (values[element.id] != value) {
toggleCollapsible(element)
@ -108,24 +109,24 @@ function createCollapsibles(node) {
function tryLoadOldCollapsibles() {
const old_map = {
"advancedPanelOpen": "editor-settings",
"modifiersPanelOpen": "editor-modifiers",
"negativePromptPanelOpen": "editor-inputs-prompt"
};
advancedPanelOpen: "editor-settings",
modifiersPanelOpen: "editor-modifiers",
negativePromptPanelOpen: "editor-inputs-prompt"
}
if (localStorage.getItem(Object.keys(old_map)[0])) {
let result = {};
Object.keys(old_map).forEach(key => {
const value = localStorage.getItem(key);
let result = {}
Object.keys(old_map).forEach((key) => {
const value = localStorage.getItem(key)
if (value !== null) {
result[old_map[key]] = (value == true || value == "true")
result[old_map[key]] = value == true || value == "true"
localStorage.removeItem(key)
}
});
})
result = JSON.stringify(result)
localStorage.setItem(COLLAPSIBLES_KEY, result)
return result
}
return null;
return null
}
function permute(arr) {
@ -134,10 +135,12 @@ function permute(arr) {
let n_permutations = Math.pow(2, n)
for (let i = 0; i < n_permutations; i++) {
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++) {
if (mask[idx] === '1' && arr[idx].trim() !== '') {
if (mask[idx] === "1" && arr[idx].trim() !== "") {
perm.push(arr[idx])
}
}
@ -152,23 +155,23 @@ function permute(arr) {
// https://stackoverflow.com/a/8212878
function millisecondsToStr(milliseconds) {
function numberEnding (number) {
return (number > 1) ? 's' : ''
function numberEnding(number) {
return number > 1 ? "s" : ""
}
let temp = Math.floor(milliseconds / 1000)
let hours = Math.floor((temp %= 86400) / 3600)
let s = ''
let s = ""
if (hours) {
s += hours + ' hour' + numberEnding(hours) + ' '
s += hours + " hour" + numberEnding(hours) + " "
}
let minutes = Math.floor((temp %= 3600) / 60)
if (minutes) {
s += minutes + ' minute' + numberEnding(minutes) + ' '
s += minutes + " minute" + numberEnding(minutes) + " "
}
let seconds = temp % 60
if (!hours && minutes < 4 && seconds) {
s += seconds + ' second' + numberEnding(seconds)
s += seconds + " second" + numberEnding(seconds)
}
return s
@ -176,101 +179,82 @@ function millisecondsToStr(milliseconds) {
// https://rosettacode.org/wiki/Brace_expansion#JavaScript
function BraceExpander() {
'use strict'
"use strict"
// Index of any closing brace matching the opening
// brace at iPosn,
// with the indices of any immediately-enclosed commas.
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],
n = (t === '{') ? (
iNest + 1
) : (t === '}' ? (
iNest - 1
) : iNest),
lst = (t === ',' && iNest === 1) ? (
lstCommas.concat(iPosn)
) : lstCommas;
n = t === "{" ? iNest + 1 : t === "}" ? iNest - 1 : iNest,
lst = t === "," && iNest === 1 ? lstCommas.concat(iPosn) : lstCommas
return n ? bracePair(tkns, iPosn + 1, n, lst) : {
close: iPosn,
commas: lst
};
return n
? bracePair(tkns, iPosn + 1, n, lst)
: {
close: iPosn,
commas: lst
}
}
// Parse of a SYNTAGM subtree
function andTree(dctSofar, tkns) {
if (!tkns.length) return [dctSofar, []];
let dctParse = dctSofar ? dctSofar : {
fn: and,
args: []
},
if (!tkns.length) return [dctSofar, []]
let dctParse = dctSofar
? dctSofar
: {
fn: and,
args: []
},
head = tkns[0],
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(
tkns, 0, 0, []
) : null,
lstOR = dctBrace && (
dctBrace.close
) && 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);
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
function orTree(dctSofar, tkns, lstCommas) {
if (!tkns.length) return [dctSofar, []];
let iLast = lstCommas.length;
if (!tkns.length) return [dctSofar, []]
let iLast = lstCommas.length
return {
fn: or,
args: splitsAt(
lstCommas, tkns
).map(function (x, i) {
let ts = x.slice(
1, i === iLast ? (
-1
) : void 0
);
args: splitsAt(lstCommas, tkns)
.map(function(x, i) {
let ts = x.slice(1, i === iLast ? -1 : void 0)
return ts.length ? ts : [''];
}).map(function (ts) {
return ts.length > 1 ? (
andTree(null, ts)[0]
) : ts[0];
})
};
return ts.length ? ts : [""]
})
.map(function(ts) {
return ts.length > 1 ? andTree(null, ts)[0] : ts[0]
})
}
}
// List of unescaped braces and commas, and remaining strings
function tokens(str) {
// Filter function excludes empty splitting artefacts
let toS = function (x) {
return x.toString();
};
let toS = function(x) {
return x.toString()
}
return str.split(/(\\\\)/).filter(toS).reduce(function (a, s) {
return a.concat(s.charAt(0) === '\\' ? s : s.split(
/(\\*[{,}])/
).filter(toS));
}, []);
return str
.split(/(\\\\)/)
.filter(toS)
.reduce(function(a, s) {
return a.concat(s.charAt(0) === "\\" ? s : s.split(/(\\*[{,}])/).filter(toS))
}, [])
}
// PARSE TREE OPERATOR (1 of 2)
@ -278,76 +262,75 @@ function BraceExpander() {
function and(args) {
let lng = args.length,
head = lng ? args[0] : null,
lstHead = "string" === typeof head ? (
[head]
) : head;
lstHead = "string" === typeof head ? [head] : head
return lng ? (
1 < lng ? lstHead.reduce(function (a, h) {
return a.concat(
and(args.slice(1)).map(function (t) {
return h + t;
})
);
}, []) : lstHead
) : [];
return lng
? 1 < lng
? lstHead.reduce(function(a, h) {
return a.concat(
and(args.slice(1)).map(function(t) {
return h + t
})
)
}, [])
: lstHead
: []
}
// PARSE TREE OPERATOR (2 of 2)
// Each option flattened
function or(args) {
return args.reduce(function (a, b) {
return a.concat(b);
}, []);
return args.reduce(function(a, b) {
return a.concat(b)
}, [])
}
// One list split into two (first sublist length n)
function splitAt(n, lst) {
return n < lst.length + 1 ? [
lst.slice(0, n), lst.slice(n)
] : [lst, []];
return n < lst.length + 1 ? [lst.slice(0, n), lst.slice(n)] : [lst, []]
}
// One list split into several (sublist lengths [n])
function splitsAt(lstN, lst) {
return lstN.reduceRight(function (a, x) {
return splitAt(x, a[0]).concat(a.slice(1));
}, [lst]);
return lstN.reduceRight(
function(a, x) {
return splitAt(x, a[0]).concat(a.slice(1))
},
[lst]
)
}
// Value of the parse tree
function evaluated(e) {
return typeof e === 'string' ? e :
e.fn(e.args.map(evaluated));
return typeof e === "string" ? e : e.fn(e.args.map(evaluated))
}
// JSON prettyprint (for parse tree, token list etc)
function pp(e) {
return JSON.stringify(e, function (k, v) {
return typeof v === 'function' ? (
'[function ' + v.name + ']'
) : v;
}, 2)
return JSON.stringify(
e,
function(k, v) {
return typeof v === "function" ? "[function " + v.name + "]" : v
},
2
)
}
// ----------------------- MAIN ------------------------
// s -> [s]
this.expand = function(s) {
// BRACE EXPRESSION PARSED
let dctParse = andTree(null, tokens(s))[0];
let dctParse = andTree(null, tokens(s))[0]
// ABSTRACT SYNTAX TREE LOGGED
// console.log(pp(dctParse));
// AST EVALUATED TO LIST OF STRINGS
return evaluated(dctParse);
return evaluated(dctParse)
}
}
/** Pause the execution of an async function until timer elapse.
* @Returns a promise that will resolve after the specified timeout.
*/
@ -360,12 +343,12 @@ function asyncDelay(timeout) {
function PromiseSource() {
const srcPromise = new Promise((resolve, reject) => {
Object.defineProperties(this, {
resolve: { value: resolve, writable: false }
, reject: { value: reject, writable: false }
resolve: { value: resolve, writable: false },
reject: { value: reject, writable: false }
})
})
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.
* @Returns a promise that will resolve to func return value.
*/
function debounce (func, wait, immediate) {
function debounce(func, wait, immediate) {
if (typeof wait === "undefined") {
wait = 40
}
@ -399,11 +382,11 @@ function debounce (func, wait, immediate) {
}
return function(...args) {
const callNow = Boolean(immediate && !timeout)
const context = this;
const context = this
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(function () {
timeout = setTimeout(function() {
if (!immediate) {
applyFn(context, args)
}
@ -418,14 +401,14 @@ function debounce (func, wait, immediate) {
}
function preventNonNumericalInput(e) {
e = e || window.event;
let charCode = (typeof e.which == "undefined") ? e.keyCode : e.which;
let charStr = String.fromCharCode(charCode);
let re = e.target.getAttribute('pattern') || '^[0-9]+$'
e = e || window.event
let charCode = typeof e.which == "undefined" ? e.keyCode : e.which
let charStr = String.fromCharCode(charCode)
let re = e.target.getAttribute("pattern") || "^[0-9]+$"
re = new RegExp(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.
*/
function getGlobal() {
if (typeof globalThis === 'object') {
if (typeof globalThis === "object") {
return globalThis
} else if (typeof global === 'object') {
} else if (typeof global === "object") {
return global
} else if (typeof self === 'object') {
} else if (typeof self === "object") {
return self
}
try {
return Function('return this')()
return Function("return this")()
} catch {
// If the Function constructor fails, we're in a browser with eval disabled by CSP headers.
return window
@ -453,18 +436,18 @@ function getGlobal() {
* @Returns true if x is an Array or a TypedArray, false otherwise.
*/
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) {
if (typeof promise !== 'object') {
throw new Error('promise is not an object.')
if (typeof promise !== "object") {
throw new Error("promise is not an object.")
}
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.
if ('isResolved' in promise || 'isRejected' in promise || 'isPending' in promise) {
if ("isResolved" in promise || "isRejected" in promise || "isPending" in promise) {
return promise
}
let isPending = true
@ -473,13 +456,13 @@ function makeQuerablePromise(promise) {
let isResolved = false
let resolvedValue = undefined
const qurPro = promise.then(
function(val){
function(val) {
isResolved = true
isPending = false
resolvedValue = val
return val
}
, function(reason) {
},
function(reason) {
rejectReason = reason
isRejected = true
isPending = false
@ -487,19 +470,19 @@ function makeQuerablePromise(promise) {
}
)
Object.defineProperties(qurPro, {
'isResolved': {
isResolved: {
get: () => isResolved
}
, 'resolvedValue': {
},
resolvedValue: {
get: () => resolvedValue
}
, 'isPending': {
},
isPending: {
get: () => isPending
}
, 'isRejected': {
},
isRejected: {
get: () => isRejected
}
, 'rejectReason': {
},
rejectReason: {
get: () => rejectReason
}
})
@ -508,25 +491,25 @@ function makeQuerablePromise(promise) {
/* inserts custom html to allow prettifying of inputs */
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") {
return
}
var parent = element.parentNode;
var parent = element.parentNode
if (!parent.classList.contains("input-toggle")) {
var wrapper = document.createElement("div");
wrapper.classList.add("input-toggle");
parent.replaceChild(wrapper, element);
wrapper.appendChild(element);
var label = document.createElement("label");
label.htmlFor = element.id;
wrapper.appendChild(label);
var wrapper = document.createElement("div")
wrapper.classList.add("input-toggle")
parent.replaceChild(wrapper, element)
wrapper.appendChild(element)
var label = document.createElement("label")
label.htmlFor = element.id
wrapper.appendChild(label)
}
})
}
class GenericEventSource {
#events = {};
#events = {}
#types = []
constructor(...eventsTypes) {
if (Array.isArray(eventsTypes) && eventsTypes.length === 1 && Array.isArray(eventsTypes[0])) {
@ -541,7 +524,7 @@ class GenericEventSource {
*/
addEventListener(name, handler) {
if (!this.#types.includes(name)) {
throw new Error('Invalid event name.')
throw new Error("Invalid event name.")
}
if (this.#events.hasOwnProperty(name)) {
this.#events[name].push(handler)
@ -574,13 +557,15 @@ class GenericEventSource {
if (evs.length <= 0) {
return Promise.resolve()
}
return Promise.allSettled(evs.map((callback) => {
try {
return Promise.resolve(callback.apply(SD, args))
} catch (ex) {
return Promise.reject(ex)
}
}))
return Promise.allSettled(
evs.map((callback) => {
try {
return Promise.resolve(callback.apply(SD, args))
} catch (ex) {
return Promise.reject(ex)
}
})
)
}
}
@ -590,7 +575,7 @@ class ServiceContainer {
constructor(...servicesParams) {
servicesParams.forEach(this.register.bind(this))
}
get services () {
get services() {
return this.#services
}
get singletons() {
@ -598,54 +583,52 @@ class ServiceContainer {
}
register(params) {
if (ServiceContainer.isConstructor(params)) {
if (typeof params.name !== 'string') {
throw new Error('params.name is not a string.')
if (typeof params.name !== "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') {
throw new Error('params is not an object.')
if (typeof params !== "object") {
throw new Error("params is not an object.")
}
[ 'name',
'definition',
].forEach((key) => {
;["name", "definition"].forEach((key) => {
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.`)
}
})
const opts = {definition: params.definition}
if ('dependencies' in params) {
const opts = { definition: params.definition }
if ("dependencies" in params) {
if (Array.isArray(params.dependencies)) {
params.dependencies.forEach((dep) => {
if (typeof dep !== 'string') {
throw new Error('dependency name is not a string.')
if (typeof dep !== "string") {
throw new Error("dependency name is not a string.")
}
})
opts.dependencies = params.dependencies
} else {
throw new Error('params.dependencies is not an array.')
throw new Error("params.dependencies is not an array.")
}
}
if (params.singleton) {
opts.singleton = true
}
this.#services.set(params.name, opts)
return Object.assign({name: params.name}, opts)
return Object.assign({ name: params.name }, opts)
}
get(name) {
const ctorInfos = this.#services.get(name)
if (!ctorInfos) {
return
}
if(!ServiceContainer.isConstructor(ctorInfos.definition)) {
if (!ServiceContainer.isConstructor(ctorInfos.definition)) {
return ctorInfos.definition
}
if(!ctorInfos.singleton) {
if (!ctorInfos.singleton) {
return this._createInstance(ctorInfos)
}
const singletonInstance = this.#singletons.get(name)
if(singletonInstance) {
if (singletonInstance) {
return singletonInstance
}
const newSingletonInstance = this._createInstance(ctorInfos)
@ -655,7 +638,7 @@ class ServiceContainer {
_getResolvedDependencies(service) {
let classDependencies = []
if(service.dependencies) {
if (service.dependencies) {
classDependencies = service.dependencies.map(this.get.bind(this))
}
return classDependencies
@ -671,10 +654,14 @@ class ServiceContainer {
}
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) {
return typeof definition === 'function'
return typeof definition === "function"
}
}
@ -693,14 +680,14 @@ function createElement(tagName, attributes, classes, textOrElements) {
if (value !== undefined && value !== null) {
element.setAttribute(key, value)
}
});
})
}
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) {
const children = Array.isArray(textOrElements) ? textOrElements : [textOrElements]
children.forEach(textOrElem => {
children.forEach((textOrElem) => {
if (textOrElem instanceof Node) {
element.appendChild(textOrElem)
} else {
@ -750,33 +737,33 @@ Array.prototype.addEventListener = function(method, callback) {
/**
* @param {CreateTabRequest} request
*/
function createTab(request) {
function createTab(request) {
if (!request?.id) {
console.error('createTab() error - id is required', Error().stack)
console.error("createTab() error - id is required", Error().stack)
return
}
if (!request.label) {
console.error('createTab() error - label is required', Error().stack)
console.error("createTab() error - label is required", Error().stack)
return
}
if (!request.icon) {
console.error('createTab() error - icon is required', Error().stack)
console.error("createTab() error - icon is required", Error().stack)
return
}
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
}
const tabsContainer = document.querySelector('.tab-container')
const tabsContainer = document.querySelector(".tab-container")
if (!tabsContainer) {
return
}
const tabsContentWrapper = document.querySelector('#tab-content-wrapper')
const tabsContentWrapper = document.querySelector("#tab-content-wrapper")
if (!tabsContentWrapper) {
return
}
@ -784,41 +771,37 @@ Array.prototype.addEventListener = function(method, callback) {
// console.debug('creating tab: ', request)
if (request.css) {
document.querySelector('body').insertAdjacentElement(
'beforeend',
createElement('style', { id: `tab-${request.id}-css` }, undefined, request.css),
)
document
.querySelector("body")
.insertAdjacentElement(
"beforeend",
createElement("style", { id: `tab-${request.id}-css` }, undefined, request.css)
)
}
const label = typeof request.label === 'function' ? request.label() : request.label
const labelElement = label instanceof Node ? label : createElement('span', undefined, undefined, label)
const label = typeof request.label === "function" ? request.label() : request.label
const labelElement = label instanceof Node ? label : createElement("span", undefined, undefined, label)
const tab = createElement(
'span',
{ id: `tab-${request.id}`, 'data-times-opened': 0 },
['tab'],
createElement(
'span',
undefined,
undefined,
[
createElement(
'i',
{ style: 'margin-right: 0.25em' },
['fa-solid', `${request.icon.startsWith('fa-') ? '' : 'fa-'}${request.icon}`, 'icon'],
),
labelElement,
],
)
"span",
{ id: `tab-${request.id}`, "data-times-opened": 0 },
["tab"],
createElement("span", undefined, undefined, [
createElement("i", { 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)
@ -826,7 +809,7 @@ Array.prototype.addEventListener = function(method, callback) {
if (resultFactory === undefined || resultFactory === null) {
return
}
const result = typeof resultFactory === 'function' ? resultFactory() : resultFactory
const result = typeof resultFactory === "function" ? resultFactory() : resultFactory
if (result instanceof Promise) {
result.then(replaceContent)
} else if (result instanceof Node) {
@ -838,7 +821,7 @@ Array.prototype.addEventListener = function(method, callback) {
replaceContent(request.content)
tab.addEventListener('click', (e) => {
tab.addEventListener("click", (e) => {
const timesOpened = +(tab.dataset.timesOpened || 0) + 1
tab.dataset.timesOpened = timesOpened
@ -848,9 +831,9 @@ Array.prototype.addEventListener = function(method, callback) {
contentElement: wrapper,
labelElement,
timesOpened,
firstOpen: timesOpened === 1,
firstOpen: timesOpened === 1
},
e,
e
)
replaceContent(result)

View File

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

View File

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

View File

@ -1,35 +1,37 @@
(function () {
;(function() {
"use strict"
const MAX_WEIGHT = 5
if (typeof editorModifierTagsList !== 'object') {
console.error('editorModifierTagsList missing...')
if (typeof editorModifierTagsList !== "object") {
console.error("editorModifierTagsList missing...")
return
}
// observe for changes in tag list
const observer = new MutationObserver(function (mutations) {
// mutations.forEach(function (mutation) {
if (editorModifierTagsList.childNodes.length > 0) {
ModifierMouseWheel(editorModifierTagsList)
}
// })
const observer = new MutationObserver(function(mutations) {
// mutations.forEach(function (mutation) {
if (editorModifierTagsList.childNodes.length > 0) {
ModifierMouseWheel(editorModifierTagsList)
}
// })
})
observer.observe(editorModifierTagsList, {
childList: true
childList: true
})
function ModifierMouseWheel(target) {
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay')
overlays.forEach (i => {
let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay")
overlays.forEach((i) => {
i.onwheel = (e) => {
if (e.ctrlKey == true) {
e.preventDefault()
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
// find the corresponding tag
for (let it = 0; it < overlays.length; it++) {
@ -38,43 +40,40 @@
break
}
}
if (s.charAt(0) !== '(' && s.charAt(s.length - 1) !== ')' && s.trim().includes(' ')) {
s = '(' + s + ')'
t = '(' + t + ')'
if (s.charAt(0) !== "(" && s.charAt(s.length - 1) !== ")" && s.trim().includes(" ")) {
s = "(" + s + ")"
t = "(" + t + ")"
}
if (delta < 0) {
// wheel scrolling up
if (s.substring(s.length - 1) == '-') {
if (s.substring(s.length - 1) == "-") {
s = s.substring(0, s.length - 1)
t = t.substring(0, t.length - 1)
}
else
{
if (s.substring(s.length - MAX_WEIGHT) !== '+'.repeat(MAX_WEIGHT)) {
s = s + '+'
t = t + '+'
} else {
if (s.substring(s.length - MAX_WEIGHT) !== "+".repeat(MAX_WEIGHT)) {
s = s + "+"
t = t + "+"
}
}
}
else{
} else {
// wheel scrolling down
if (s.substring(s.length - 1) == '+') {
if (s.substring(s.length - 1) == "+") {
s = s.substring(0, s.length - 1)
t = t.substring(0, t.length - 1)
}
else
{
if (s.substring(s.length - MAX_WEIGHT) !== '-'.repeat(MAX_WEIGHT)) {
s = s + '-'
t = t + '-'
} else {
if (s.substring(s.length - MAX_WEIGHT) !== "-".repeat(MAX_WEIGHT)) {
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)
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
for (let it = 0; it < overlays.length; it++) {
if (i == overlays[it]) {
@ -82,7 +81,7 @@
break
}
}
document.dispatchEvent(new Event('refreshImageModifiers'))
document.dispatchEvent(new Event("refreshImageModifiers"))
}
}
})

View File

@ -1,25 +1,25 @@
(function() {
PLUGINS['MODIFIERS_LOAD'].push({
;(function() {
PLUGINS["MODIFIERS_LOAD"].push({
loader: function() {
let customModifiers = localStorage.getItem(CUSTOM_MODIFIERS_KEY, '')
let customModifiers = localStorage.getItem(CUSTOM_MODIFIERS_KEY, "")
customModifiersTextBox.value = customModifiers
if (customModifiersGroupElement !== undefined) {
customModifiersGroupElement.remove()
}
if (customModifiers && customModifiers.trim() !== '') {
customModifiers = customModifiers.split('\n')
customModifiers = customModifiers.filter(m => m.trim() !== '')
if (customModifiers && customModifiers.trim() !== "") {
customModifiers = customModifiers.split("\n")
customModifiers = customModifiers.filter((m) => m.trim() !== "")
customModifiers = customModifiers.map(function(m) {
return {
"modifier": m
modifier: m
}
})
let customGroup = {
'category': 'Custom Modifiers',
'modifiers': customModifiers
category: "Custom Modifiers",
modifiers: customModifiers
}
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
source files or spec files are loaded.
*/
(function() {
const jasmineRequire = window.jasmineRequire || require('./jasmine.js');
;(function() {
const jasmineRequire = window.jasmineRequire || require("./jasmine.js")
/**
* ## Require &amp; Instantiate
*
* Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
*/
const jasmine = jasmineRequire.core(jasmineRequire),
global = jasmine.getGlobal();
global.jasmine = jasmine;
/**
* ## Require &amp; Instantiate
*
* Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
*/
const jasmine = jasmineRequire.core(jasmineRequire),
global = jasmine.getGlobal()
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.
*/
jasmineRequire.html(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.
*/
jasmineRequire.html(jasmine)
/**
* Create the Jasmine environment. This is used to run all specs in a project.
*/
const env = jasmine.getEnv();
/**
* Create the Jasmine environment. This is used to run all specs in a project.
*/
const env = jasmine.getEnv()
/**
* ## 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.
*/
const jasmineInterface = jasmineRequire.interface(jasmine, env);
/**
* ## 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.
*/
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`.
*/
for (const property in jasmineInterface) {
global[property] = jasmineInterface[property];
}
})();
/**
* 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) {
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.
*/
(function() {
const env = jasmine.getEnv();
;(function() {
const env = jasmine.getEnv()
/**
* ## 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.
*/
/**
* ## 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.
*/
const queryString = new jasmine.QueryString({
getWindowLocation: function() {
return window.location;
const queryString = new jasmine.QueryString({
getWindowLocation: function() {
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 = {
stopOnSpecFailure: queryString.getParam('stopOnSpecFailure'),
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');
if (random !== undefined && random !== "") {
config.random = random
}
});
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();
const seed = queryString.getParam("seed")
if (seed) {
config.seed = seed
}
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)}`
beforeEach(function () {
beforeEach(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 60 * 1000 // Test timeout after 15 minutes
jasmine.addMatchers({
toBeOneOf: function () {
toBeOneOf: function() {
return {
compare: function (actual, expected) {
compare: function(actual, expected) {
return {
pass: expected.includes(actual)
}
@ -16,20 +16,20 @@ beforeEach(function () {
}
})
})
describe('stable-diffusion-ui', function() {
describe("stable-diffusion-ui", function() {
beforeEach(function() {
expect(typeof SD).toBe('object')
expect(typeof SD.serverState).toBe('object')
expect(typeof SD.serverState.status).toBe('string')
expect(typeof SD).toBe("object")
expect(typeof SD.serverState).toBe("object")
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)
SD.sessionId = JASMINE_SESSION_ID
await SD.init()
expect(SD.isServerAvailable()).toBeTrue()
})
it('enfore the current task state', function() {
it("enfore the current task state", function() {
const task = new SD.Task()
expect(task.status).toBe(SD.TaskStatus.init)
expect(task.isPending).toBeTrue()
@ -65,149 +65,161 @@ describe('stable-diffusion-ui', function() {
task._setStatus(SD.TaskStatus.completed)
}).toThrowError()
})
it('should be able to run tasks', async function() {
expect(typeof SD.Task.run).toBe('function')
it("should be able to run tasks", async function() {
expect(typeof SD.Task.run).toBe("function")
const promiseGenerator = (function*(val) {
expect(val).toBe('start')
expect(val).toBe("start")
expect(yield 1 + 1).toBe(4)
expect(yield 2 + 2).toBe(8)
yield asyncDelay(500)
expect(yield 3 + 3).toBe(12)
expect(yield 4 + 4).toBe(16)
return 8 + 8
})('start')
const callback = function({value, done}) {
return {value: 2 * value, done}
})("start")
const callback = function({ 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() {
expect(typeof SD.Task.enqueue).toBe('function')
it("should be able to queue tasks", async function() {
expect(typeof SD.Task.enqueue).toBe("function")
const promiseGenerator = (function*(val) {
expect(val).toBe('start')
expect(val).toBe("start")
expect(yield 1 + 1).toBe(4)
expect(yield 2 + 2).toBe(8)
yield asyncDelay(500)
expect(yield 3 + 3).toBe(12)
expect(yield 4 + 4).toBe(16)
return 8 + 8
})('start')
const callback = function({value, done}) {
return {value: 2 * value, done}
})("start")
const callback = function({ 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)
})
it('should be able to chain handlers', async function() {
expect(typeof SD.Task.enqueue).toBe('function')
it("should be able to chain handlers", async function() {
expect(typeof SD.Task.enqueue).toBe("function")
const promiseGenerator = (function*(val) {
expect(val).toBe('start')
expect(yield {test: '1'}).toEqual({test: '1', foo: 'bar'})
expect(val).toBe("start")
expect(yield { test: "1" }).toEqual({ test: "1", foo: "bar" })
expect(yield 2 + 2).toEqual(8)
yield asyncDelay(500)
expect(yield 3 + 3).toEqual(12)
expect(yield {test: 4}).toEqual({test: 8, foo: 'bar'})
return {test: 8}
})('start')
const gen1 = SD.Task.asGenerator({generator: promiseGenerator, callback: function({value, done}) {
if (typeof value === "object") {
value['foo'] = 'bar'
expect(yield { test: 4 }).toEqual({ test: 8, foo: "bar" })
return { test: 8 }
})("start")
const gen1 = SD.Task.asGenerator({
generator: promiseGenerator,
callback: function({ value, done }) {
if (typeof value === "object") {
value["foo"] = "bar"
}
return { value, done }
}
return {value, done}
}})
const gen2 = SD.Task.asGenerator({generator: gen1, callback: function({value, done}) {
if (typeof value === 'number') {
value = 2 * value
})
const gen2 = SD.Task.asGenerator({
generator: gen1,
callback: function({ value, done }) {
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
}
return {value, done}
}})
expect(await SD.Task.enqueue(gen2)).toEqual({test:32, foo: 'bar'})
})
expect(await SD.Task.enqueue(gen2)).toEqual({ test: 32, foo: "bar" })
})
describe('ServiceContainer', function() {
it('should be able to register providers', function() {
describe("ServiceContainer", function() {
it("should be able to register providers", function() {
const cont = new ServiceContainer(
function foo() {
this.bar = ''
this.bar = ""
},
function bar() {
return () => 0
},
{ name: 'zero', definition: 0 },
{ name: 'ctx', definition: () => Object.create(null), singleton: true },
{ name: 'test',
{ name: "zero", definition: 0 },
{ name: "ctx", definition: () => Object.create(null), singleton: true },
{
name: "test",
definition: (ctx, missing, one, foo) => {
expect(ctx).toEqual({ran: true})
expect(ctx).toEqual({ ran: true })
expect(one).toBe(1)
expect(typeof foo).toBe('object')
expect(typeof foo).toBe("object")
expect(foo.bar).toBeDefined()
expect(typeof missing).toBe('undefined')
return {foo: 'bar'}
}, dependencies: ['ctx', 'missing', 'one', 'foo']
expect(typeof missing).toBe("undefined")
return { foo: "bar" }
},
dependencies: ["ctx", "missing", "one", "foo"]
}
)
const fooObj = cont.get('foo')
expect(typeof fooObj).toBe('object')
const fooObj = cont.get("foo")
expect(typeof fooObj).toBe("object")
fooObj.ran = true
const ctx = cont.get('ctx')
const ctx = cont.get("ctx")
expect(ctx).toEqual({})
ctx.ran = true
const bar = cont.get('bar')
expect(typeof bar).toBe('function')
const bar = cont.get("bar")
expect(typeof bar).toBe("function")
expect(bar()).toBe(0)
cont.register({name: 'one', definition: 1})
const test = cont.get('test')
expect(typeof test).toBe('object')
expect(test.foo).toBe('bar')
cont.register({ name: "one", definition: 1 })
const test = cont.get("test")
expect(typeof test).toBe("object")
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()
const nbr_steps = 15
let res = await fetch('/render', {
method: 'POST',
let res = await fetch("/render", {
method: "POST",
headers: {
'Content-Type': 'application/json'
"Content-Type": "application/json"
},
body: JSON.stringify({
"prompt": "a photograph of an astronaut riding a horse",
"negative_prompt": "",
"width": 128,
"height": 128,
"seed": Math.floor(Math.random() * 10000000),
prompt: "a photograph of an astronaut riding a horse",
negative_prompt: "",
width: 128,
height: 128,
seed: Math.floor(Math.random() * 10000000),
"sampler": "plms",
"use_stable_diffusion_model": "sd-v1-4",
"num_inference_steps": nbr_steps,
"guidance_scale": 7.5,
sampler: "plms",
use_stable_diffusion_model: "sd-v1-4",
num_inference_steps: nbr_steps,
guidance_scale: 7.5,
"numOutputsParallel": 1,
"stream_image_progress": true,
"show_only_filtered_image": true,
"output_format": "jpeg",
numOutputsParallel: 1,
stream_image_progress: true,
show_only_filtered_image: true,
output_format: "jpeg",
"session_id": JASMINE_SESSION_ID,
}),
session_id: JASMINE_SESSION_ID
})
})
expect(res.ok).toBeTruthy()
const renderRequest = await res.json()
expect(typeof renderRequest.stream).toBe('string')
expect(typeof renderRequest.stream).toBe("string")
expect(renderRequest.task).toBeDefined()
// Wait for server status to update.
await SD.waitUntil(() => {
console.log('Waiting for %s to be received...', renderRequest.task)
return (!SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)])
}, 250, 10 * 60 * 1000)
await SD.waitUntil(
() => {
console.log("Waiting for %s to be received...", renderRequest.task)
return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)]
},
250,
10 * 60 * 1000
)
// Wait for task to start on server.
await SD.waitUntil(() => {
console.log('Waiting for %s to start...', renderRequest.task)
return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)] !== 'pending'
console.log("Waiting for %s to start...", renderRequest.task)
return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)] !== "pending"
}, 250)
const reader = new SD.ChunkedStreamReader(renderRequest.stream)
@ -217,24 +229,24 @@ describe('stable-diffusion-ui', function() {
if (!value || value.length <= 0) {
return
}
return reader.readStreamAsJSON(value.join(''))
return reader.readStreamAsJSON(value.join(""))
}
reader.onNext = function({done, value}) {
reader.onNext = function({ done, value }) {
console.log(value)
if (typeof value === 'object' && 'status' in value) {
if (typeof value === "object" && "status" in value) {
done = true
}
return {done, value}
return { done, value }
}
let lastUpdate = undefined
let stepCount = 0
let complete = false
//for await (const stepUpdate of reader) {
for await (const stepUpdate of reader.open()) {
console.log('ChunkedStreamReader received ', stepUpdate)
console.log("ChunkedStreamReader received ", stepUpdate)
lastUpdate = stepUpdate
if (complete) {
expect(stepUpdate.status).toBe('succeeded')
expect(stepUpdate.status).toBe("succeeded")
expect(stepUpdate.output).toHaveSize(1)
} else {
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)
expect(res.ok).toBeTruthy()
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)
}
})
describe('should be able to make renders', function() {
describe("should be able to make renders", function() {
beforeEach(function() {
expect(SD.isServerAvailable()).toBeTrue()
})
it('basic inline request', async function() {
it("basic inline request", async function() {
let stepCount = 0
let complete = false
const result = await SD.render({
"prompt": "a photograph of an astronaut riding a horse",
"width": 128,
"height": 128,
"num_inference_steps": 10,
"show_only_filtered_image": false,
//"use_face_correction": 'GFPGANv1.3',
"use_upscale": "RealESRGAN_x4plus",
"session_id": JASMINE_SESSION_ID,
}, function(event) {
console.log(this, event)
if ('update' in event) {
const stepUpdate = event.update
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
expect(stepUpdate.status).toBe('succeeded')
expect(stepUpdate.output).toHaveSize(2)
} else {
expect(stepUpdate.step).toBe(stepCount)
if (stepUpdate.step === stepUpdate.total_steps) {
complete = true
const result = await SD.render(
{
prompt: "a photograph of an astronaut riding a horse",
width: 128,
height: 128,
num_inference_steps: 10,
show_only_filtered_image: false,
//"use_face_correction": 'GFPGANv1.3',
use_upscale: "RealESRGAN_x4plus",
session_id: JASMINE_SESSION_ID
},
function(event) {
console.log(this, event)
if ("update" in event) {
const stepUpdate = event.update
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
expect(stepUpdate.status).toBe("succeeded")
expect(stepUpdate.output).toHaveSize(2)
} else {
stepCount++
expect(stepUpdate.step).toBe(stepCount)
if (stepUpdate.step === stepUpdate.total_steps) {
complete = true
} else {
stepCount++
}
}
}
}
})
)
console.log(result)
expect(result.status).toBe('succeeded')
expect(result.status).toBe("succeeded")
expect(result.output).toHaveSize(2)
})
it('post and reader request', async function() {
it("post and reader request", async function() {
const renderTask = new SD.RenderTask({
"prompt": "a photograph of an astronaut riding a horse",
"width": 128,
"height": 128,
"seed": SD.MAX_SEED_VALUE,
"num_inference_steps": 10,
"session_id": JASMINE_SESSION_ID,
prompt: "a photograph of an astronaut riding a horse",
width: 128,
height: 128,
seed: SD.MAX_SEED_VALUE,
num_inference_steps: 10,
session_id: JASMINE_SESSION_ID
})
expect(renderTask.status).toBe(SD.TaskStatus.init)
const timeout = -1
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.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)
let stepCount = 0
@ -318,7 +336,7 @@ describe('stable-diffusion-ui', function() {
for await (const stepUpdate of renderTask.reader.open()) {
console.log(stepUpdate)
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
expect(stepUpdate.status).toBe('succeeded')
expect(stepUpdate.status).toBe("succeeded")
expect(stepUpdate.output).toHaveSize(1)
} else {
expect(stepUpdate.step).toBe(stepCount)
@ -330,28 +348,28 @@ describe('stable-diffusion-ui', function() {
}
}
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)
})
it('queued request', async function() {
it("queued request", async function() {
let stepCount = 0
let complete = false
const renderTask = new SD.RenderTask({
"prompt": "a photograph of an astronaut riding a horse",
"width": 128,
"height": 128,
"num_inference_steps": 10,
"show_only_filtered_image": false,
prompt: "a photograph of an astronaut riding a horse",
width: 128,
height: 128,
num_inference_steps: 10,
show_only_filtered_image: false,
//"use_face_correction": 'GFPGANv1.3',
"use_upscale": "RealESRGAN_x4plus",
"session_id": JASMINE_SESSION_ID,
use_upscale: "RealESRGAN_x4plus",
session_id: JASMINE_SESSION_ID
})
await renderTask.enqueue(function(event) {
console.log(this, event)
if ('update' in event) {
if ("update" in event) {
const stepUpdate = event.update
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
expect(stepUpdate.status).toBe('succeeded')
expect(stepUpdate.status).toBe("succeeded")
expect(stepUpdate.output).toHaveSize(2)
} else {
expect(stepUpdate.step).toBe(stepCount)
@ -364,12 +382,12 @@ describe('stable-diffusion-ui', function() {
}
})
console.log(renderTask.result)
expect(renderTask.result.status).toBe('succeeded')
expect(renderTask.result.status).toBe("succeeded")
expect(renderTask.result.output).toHaveSize(2)
})
})
describe('# Special cases', function() {
it('should throw an exception on set for invalid sessionId', function() {
describe("# Special cases", function() {
it("should throw an exception on set for invalid sessionId", function() {
expect(function() {
SD.sessionId = undefined
}).toThrowError("Can't set sessionId to undefined.")
@ -386,16 +404,17 @@ if (!PLUGINS.SELFTEST) {
PLUGINS.SELFTEST = {}
}
loadUIPlugins().then(function() {
console.log('loadCompleted', loadEvent)
describe('@Plugins', function() {
it('exposes hooks to overide', function() {
expect(typeof PLUGINS.IMAGE_INFO_BUTTONS).toBe('object')
expect(typeof PLUGINS.TASK_CREATE).toBe('object')
console.log("loadCompleted", loadEvent)
describe("@Plugins", function() {
it("exposes hooks to overide", function() {
expect(typeof PLUGINS.IMAGE_INFO_BUTTONS).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))
if (!pluginsTests || pluginsTests.length <= 0) {
it('but nothing loaded...', function() {
it("but nothing loaded...", function() {
expect(true).toBeTruthy()
})
return

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
/* SD-UI Selftest Plugin.js
*/
(function() { "use strict"
;(function() {
"use strict"
const ID_PREFIX = "selftest-plugin"
const links = document.getElementById("community-links")
@ -10,16 +11,18 @@
}
// Add link to Jasmine SpecRunner
const pluginLink = document.createElement('li')
const pluginLink = document.createElement("li")
const options = {
'stopSpecOnExpectationFailure': "true",
'stopOnSpecFailure': 'false',
'random': 'false',
'hideDisabled': 'false'
stopSpecOnExpectationFailure: "true",
stopOnSpecFailure: "false",
random: "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>`
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==