forked from extern/easydiffusion
9ee38d0b70
Long custom modifiers in a disabled state (e.g. right-click on the image tag) are properly restored as disabled but are incorrectly shown as "active" in the UI. I somehow missed that in https://github.com/cmdr2/stable-diffusion-ui/pull/1062, so here is the fix for that. This change only applies to plugins that try to restore image modifiers, no change to the regular UI.
390 lines
14 KiB
JavaScript
390 lines
14 KiB
JavaScript
let activeTags = []
|
|
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')
|
|
|
|
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')
|
|
let style = previewImageField.value
|
|
let styleIndex = (style=='portrait') ? 0 : 1
|
|
|
|
modifierCard.className = 'modifier-card'
|
|
modifierCard.innerHTML = `
|
|
<div class="modifier-card-overlay"></div>
|
|
<div class="modifier-card-image-container">
|
|
<div class="modifier-card-image-overlay">+</div>
|
|
<p class="modifier-card-error-label"></p>
|
|
<img onerror="this.remove()" alt="Modifier Image" class="modifier-card-image">
|
|
</div>
|
|
<div class="modifier-card-container">
|
|
<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')
|
|
|
|
errorText.innerText = 'No Image'
|
|
|
|
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
|
|
|
|
if(cardLabel.length <= maxLabelLength) {
|
|
label.querySelector('p').innerText = cardLabel
|
|
} else {
|
|
const tooltipText = document.createElement('span')
|
|
tooltipText.className = 'tooltip-text'
|
|
tooltipText.innerText = name
|
|
|
|
label.classList.add('tooltip')
|
|
label.appendChild(tooltipText)
|
|
|
|
label.querySelector('p').innerText = cardLabel.substring(0, maxLabelLength) + '...'
|
|
}
|
|
label.querySelector('p').dataset.fullName = name // preserve the full name
|
|
|
|
return modifierCard
|
|
}
|
|
|
|
function createModifierGroup(modifierGroup, initiallyExpanded, removeBy) {
|
|
const title = modifierGroup.category
|
|
const modifiers = modifierGroup.modifiers
|
|
|
|
const titleEl = document.createElement('h5')
|
|
titleEl.className = 'collapsible'
|
|
titleEl.innerText = title
|
|
|
|
const modifiersEl = document.createElement('div')
|
|
modifiersEl.classList.add('collapsible-content', 'editor-modifiers-leaf')
|
|
|
|
if (initiallyExpanded === true) {
|
|
titleEl.className += ' active'
|
|
}
|
|
|
|
modifiers.forEach(modObj => {
|
|
const modifierName = modObj.modifier
|
|
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') {
|
|
modifiersEl.appendChild(modifierCard)
|
|
const trimmedName = trimModifiers(modifierName)
|
|
|
|
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)
|
|
toggleCardState(trimmedName, false)
|
|
} else {
|
|
// add modifier to active array
|
|
activeTags.push({
|
|
'name': modifierName,
|
|
'element': modifierCard.cloneNode(true),
|
|
'originElement': modifierCard,
|
|
'previews': modifierPreviews
|
|
})
|
|
toggleCardState(trimmedName, true)
|
|
}
|
|
|
|
refreshTagsList()
|
|
document.dispatchEvent(new Event('refreshImageModifiers'))
|
|
})
|
|
}
|
|
})
|
|
|
|
let brk = document.createElement('br')
|
|
brk.style.clear = 'both'
|
|
modifiersEl.appendChild(brk)
|
|
|
|
let e = document.createElement('div')
|
|
e.className = 'modifier-category'
|
|
e.appendChild(titleEl)
|
|
e.appendChild(modifiersEl)
|
|
|
|
editorModifierEntries.insertBefore(e, customModifierEntriesToolbar.nextSibling)
|
|
|
|
return e
|
|
}
|
|
|
|
function trimModifiers(tag) {
|
|
// Remove trailing '-' and/or '+'
|
|
tag = tag.replace(/[-+]+$/, '');
|
|
// Remove parentheses at beginning and end
|
|
return tag.replace(/^[(]+|[\s)]+$/g, '');
|
|
}
|
|
|
|
async function loadModifiers() {
|
|
try {
|
|
let res = await fetch('/get/modifiers')
|
|
if (res.status === 200) {
|
|
res = await res.json()
|
|
|
|
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
|
|
})
|
|
|
|
createCollapsibles(editorModifierEntries)
|
|
}
|
|
} catch (e) {
|
|
console.error('error fetching modifiers', e)
|
|
}
|
|
|
|
loadCustomModifiers()
|
|
resizeModifierCards(modifierCardSizeSlider.value)
|
|
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 = '+'
|
|
}
|
|
})
|
|
activeTags = []
|
|
|
|
// set new modifiers
|
|
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
|
|
})
|
|
}
|
|
modifierCard.classList.add(activeCardClass)
|
|
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-'
|
|
found = true
|
|
}
|
|
})
|
|
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)) {
|
|
// remove modifier from active array
|
|
activeTags = activeTags.filter(x => x.name != tag)
|
|
modifierCard.classList.remove(activeCardClass)
|
|
|
|
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+'
|
|
}
|
|
refreshTagsList()
|
|
})
|
|
|
|
activeTags.push({
|
|
'name': tag,
|
|
'element': modifierCard,
|
|
'originElement': undefined // no origin element for missing tags
|
|
})
|
|
}
|
|
})
|
|
refreshTagsList(inactiveTags)
|
|
}
|
|
|
|
function refreshInactiveTags(inactiveTags) {
|
|
// update inactive tags
|
|
if (inactiveTags !== undefined && inactiveTags.length > 0) {
|
|
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')
|
|
}
|
|
})
|
|
}
|
|
|
|
function refreshTagsList(inactiveTags) {
|
|
editorModifierTagsList.innerHTML = ''
|
|
|
|
if (activeTags.length == 0) {
|
|
editorTagsContainer.style.display = 'none'
|
|
return
|
|
} else {
|
|
editorTagsContainer.style.display = 'block'
|
|
}
|
|
|
|
activeTags.forEach((tag, index) => {
|
|
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 })
|
|
|
|
if (idx !== -1) {
|
|
toggleCardState(activeTags[idx].name, false)
|
|
|
|
activeTags.splice(idx, 1)
|
|
refreshTagsList()
|
|
}
|
|
document.dispatchEvent(new Event('refreshImageModifiers'))
|
|
})
|
|
})
|
|
|
|
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
|
|
}
|
|
|
|
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 = '-'
|
|
}
|
|
else{
|
|
card.classList.remove(activeCardClass)
|
|
card.querySelector('.modifier-card-image-overlay').innerText = '+'
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
function changePreviewImages(val) {
|
|
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 => {
|
|
let obj = {}
|
|
|
|
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()
|
|
|
|
const previews = previewArr.find(preview => relativePreviewPath == preview[currentPreviewType])
|
|
|
|
if(typeof previews == 'object') {
|
|
let preview = null
|
|
|
|
if (val == 'portrait') {
|
|
preview = previews.portrait
|
|
}
|
|
else if (val == 'landscape') {
|
|
preview = previews.landscape
|
|
}
|
|
|
|
if(preview != null) {
|
|
previewImage.src = `${modifierThumbnailPath}/${preview}`
|
|
previewImage.setAttribute('preview-type', val)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
function resizeModifierCards(val) {
|
|
const cardSizePrefix = 'modifier-card-size_'
|
|
const modifierCardClass = 'modifier-card'
|
|
|
|
const modifierCards = document.querySelectorAll(`.${modifierCardClass}`)
|
|
const cardSize = n => `${cardSizePrefix}${n}`
|
|
|
|
modifierCards.forEach(card => {
|
|
// remove existing size classes
|
|
const classes = card.className.split(' ').filter(c => !c.startsWith(cardSizePrefix))
|
|
card.className = classes.join(' ').trim()
|
|
|
|
if(val != 0) {
|
|
card.classList.add(cardSize(val))
|
|
}
|
|
})
|
|
}
|
|
|
|
modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value)
|
|
previewImageField.onchange = () => changePreviewImages(previewImageField.value)
|
|
|
|
modifierSettingsBtn.addEventListener('click', function(e) {
|
|
modifierSettingsOverlay.classList.add("active")
|
|
customModifiersTextBox.setSelectionRange(0, 0)
|
|
customModifiersTextBox.focus()
|
|
customModifiersInitialContent = customModifiersTextBox.value // preserve the initial content
|
|
e.stopPropagation()
|
|
})
|
|
|
|
modifierSettingsOverlay.addEventListener('keydown', function(e) {
|
|
switch (e.key) {
|
|
case "Escape": // Escape to cancel
|
|
customModifiersTextBox.value = customModifiersInitialContent // undo the changes
|
|
modifierSettingsOverlay.classList.remove("active")
|
|
e.stopPropagation()
|
|
break
|
|
case "Enter":
|
|
if (e.ctrlKey) { // Ctrl+Enter to confirm
|
|
modifierSettingsOverlay.classList.remove("active")
|
|
e.stopPropagation()
|
|
break
|
|
}
|
|
}
|
|
})
|
|
|
|
function saveCustomModifiers() {
|
|
localStorage.setItem(CUSTOM_MODIFIERS_KEY, customModifiersTextBox.value.trim())
|
|
|
|
loadCustomModifiers()
|
|
}
|
|
|
|
function loadCustomModifiers() {
|
|
PLUGINS['MODIFIERS_LOAD'].forEach(fn=>fn.loader.call())
|
|
}
|
|
|
|
customModifiersTextBox.addEventListener('change', saveCustomModifiers)
|