diff --git a/ui/index.html b/ui/index.html index a50639dd..7a5abee7 100644 --- a/ui/index.html +++ b/ui/index.html @@ -251,7 +251,9 @@ + + diff --git a/ui/media/js/image-modifiers.js b/ui/media/js/image-modifiers.js new file mode 100644 index 00000000..cb051583 --- /dev/null +++ b/ui/media/js/image-modifiers.js @@ -0,0 +1,230 @@ +let activeTags = [] +let modifiers = [] + +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') + +const modifierThumbnailPath = 'media/modifier-thumbnails' +const activeCardClass = 'modifier-card-active' + +function createModifierCard(name, previews) { + const modifierCard = document.createElement('div') + modifierCard.className = 'modifier-card' + modifierCard.innerHTML = ` +
+
+
+
+

+ Modifier Image +
+
+

+
` + + 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[0]; // portrait + image.setAttribute('preview-type', 'portrait') + } else { + image.remove() + } + + const maxLabelLength = 30 + const nameWithoutBy = name.replace('by ', '') + + if(nameWithoutBy.length <= maxLabelLength) { + label.querySelector('p').innerText = nameWithoutBy + } else { + const tooltipText = document.createElement('span') + tooltipText.className = 'tooltip-text' + tooltipText.innerText = name + + label.classList.add('tooltip') + label.appendChild(tooltipText) + + label.querySelector('p').innerText = nameWithoutBy.substring(0, maxLabelLength) + '...' + } + + return modifierCard +} + +async function loadModifiers() { + try { + let res = await fetch('/get/modifiers') + if (res.status === 200) { + res = await res.json() + + modifiers = res; // update global variable + + res.forEach((modifierGroup, idx) => { + 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 (idx == 0) { + titleEl.className += ' active' + modifiersEl.style.display = 'block' + } + + modifiers.forEach(modObj => { + const modifierName = modObj.modifier + const modifierPreviews = modObj?.previews?.map(preview => `${modifierThumbnailPath}/${preview.path}`) + + const modifierCard = createModifierCard(modifierName, modifierPreviews) + + if(typeof modifierCard == 'object') { + modifiersEl.appendChild(modifierCard) + + modifierCard.addEventListener('click', () => { + if (activeTags.map(x => x.name).includes(modifierName)) { + // remove modifier from active array + activeTags = activeTags.filter(x => x.name != modifierName) + modifierCard.classList.remove(activeCardClass) + + modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+' + } else { + // add modifier to active array + activeTags.push({ + 'name': modifierName, + 'element': modifierCard.cloneNode(true), + 'originElement': modifierCard, + 'previews': modifierPreviews + }) + + modifierCard.classList.add(activeCardClass) + + modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-' + } + + refreshTagsList() + }) + } + }) + + let brk = document.createElement('br') + brk.style.clear = 'both' + modifiersEl.appendChild(brk) + + let e = document.createElement('div') + e.appendChild(titleEl) + e.appendChild(modifiersEl) + + editorModifierEntries.appendChild(e) + }) + + createCollapsibles(editorModifierEntries) + } + } catch (e) { + console.log('error fetching modifiers', e) + } +} + +function refreshTagsList() { + 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.indexOf(tag) + + if (idx !== -1) { + activeTags[idx].originElement.classList.remove(activeCardClass) + activeTags[idx].originElement.querySelector('.modifier-card-image-overlay').innerText = '+' + + activeTags.splice(idx, 1) + refreshTagsList() + } + }) + }) + + let brk = document.createElement('br') + brk.style.clear = 'both' + editorModifierTagsList.appendChild(brk) +} + +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) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 99248468..35c43041 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -79,16 +79,7 @@ let clearAllPreviewsBtn = document.querySelector("#clear-all-previews") let maskSetting = document.querySelector('#enable_mask') let negativePromptPanelHandle = document.querySelector('#negative_prompt_handle') -let editorModifierEntries = document.querySelector('#editor-modifiers-entries') -let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list') -let editorTagsContainer = document.querySelector('#editor-inputs-tags-container') - let imagePreview = document.querySelector("#preview") -let previewImageField = document.querySelector('#preview-image') -previewImageField.onchange = () => changePreviewImages(previewImageField.value) - -let modifierCardSizeSlider = document.querySelector('#modifier-card-size-slider') -modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value) // let previewPrompt = document.querySelector('#preview-prompt') @@ -114,17 +105,12 @@ maskResetButton.style.fontWeight = 'normal' maskResetButton.style.fontSize = '10pt' let serverState = {'status': 'Offline', 'time': Date.now()} -let activeTags = [] -let modifiers = [] let lastPromptUsed = '' let bellPending = false let taskQueue = [] let currentTask = null -const modifierThumbnailPath = 'media/modifier-thumbnails' -const activeCardClass = 'modifier-card-active' - function getLocalStorageItem(key, fallback) { let item = localStorage.getItem(key) if (item === null) { @@ -1002,28 +988,6 @@ function permutePrompts(promptBase, promptMatrix) { return prompts } -function permute(arr) { - let permutations = [] - let n = arr.length - 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') - - for (let idx = 0; idx < mask.length; idx++) { - if (mask[idx] === '1' && arr[idx].trim() !== '') { - perm.push(arr[idx]) - } - } - - if (perm.length > 0) { - permutations.push(perm) - } - } - - return permutations -} - // create a file name with embedded prompt and metadata // for easier cateloging and comparison function createFileName(prompt, seed, steps, guidance, outputFormat) { @@ -1370,150 +1334,6 @@ promptsFromFileSelector.addEventListener('change', function() { } }) -// function showMaskImagePreview() { -// if (maskImageSelector.files.length === 0) { -// // maskImagePreviewContainer.style.display = 'none' -// return -// } - -// let reader = new FileReader() -// let file = maskImageSelector.files[0] - -// reader.addEventListener('load', function() { -// // maskImagePreview.src = reader.result -// // maskImagePreviewContainer.style.display = 'block' -// }) - -// if (file) { -// reader.readAsDataURL(file) -// } -// } -// maskImageSelector.addEventListener('change', showMaskImagePreview) -// showMaskImagePreview() - -// maskImageClearBtn.addEventListener('click', function() { -// maskImageSelector.value = null -// maskImagePreview.src = '' -// // maskImagePreviewContainer.style.display = 'none' -// }) - -// https://stackoverflow.com/a/8212878 -function millisecondsToStr(milliseconds) { - function numberEnding (number) { - return (number > 1) ? 's' : '' - } - - var temp = Math.floor(milliseconds / 1000) - var hours = Math.floor((temp %= 86400) / 3600) - var s = '' - if (hours) { - s += hours + ' hour' + numberEnding(hours) + ' ' - } - var minutes = Math.floor((temp %= 3600) / 60) - if (minutes) { - s += minutes + ' minute' + numberEnding(minutes) + ' ' - } - var seconds = temp % 60 - if (!hours && minutes < 4 && seconds) { - s += seconds + ' second' + numberEnding(seconds) - } - - return s -} - -// https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/ -function getNextSibling(elem, selector) { - // Get the next sibling element - var sibling = elem.nextElementSibling - - // If there's no selector, return the first sibling - if (!selector) return sibling - - // If the sibling matches our selector, use it - // If not, jump to the next sibling and continue the loop - while (sibling) { - if (sibling.matches(selector)) return sibling - sibling = sibling.nextElementSibling - } -} - -function createCollapsibles(node) { - if (!node) { - node = document - } - - let collapsibles = node.querySelectorAll(".collapsible") - collapsibles.forEach(function(c) { - let handle = document.createElement('span') - handle.className = 'collapsible-handle' - - if (c.className.indexOf('active') !== -1) { - handle.innerHTML = '➖' // minus - } else { - handle.innerHTML = '➕' // plus - } - c.insertBefore(handle, c.firstChild) - - c.addEventListener('click', function() { - this.classList.toggle("active") - let content = getNextSibling(this, '.collapsible-content') - if (content.style.display === "block") { - content.style.display = "none" - handle.innerHTML = '➕' // plus - } else { - content.style.display = "block" - handle.innerHTML = '➖' // minus - } - - if (this == advancedPanelHandle) { - let state = (content.style.display === 'block' ? 'true' : 'false') - localStorage.setItem(ADVANCED_PANEL_OPEN_KEY, state) - } else if (this == modifiersPanelHandle) { - let state = (content.style.display === 'block' ? 'true' : 'false') - localStorage.setItem(MODIFIERS_PANEL_OPEN_KEY, state) - } else if (this == negativePromptPanelHandle) { - let state = (content.style.display === 'block' ? 'true' : 'false') - localStorage.setItem(NEGATIVE_PROMPT_PANEL_OPEN_KEY, state) - } - }) - }) -} -createCollapsibles() - -function refreshTagsList() { - 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.indexOf(tag) - - if (idx !== -1) { - activeTags[idx].originElement.classList.remove(activeCardClass) - activeTags[idx].originElement.querySelector('.modifier-card-image-overlay').innerText = '+' - - activeTags.splice(idx, 1) - refreshTagsList() - } - }) - }) - - let brk = document.createElement('br') - brk.style.clear = 'both' - editorModifierTagsList.appendChild(brk) -} - async function getDiskPath() { try { let diskPath = getSavedDiskPath() @@ -1535,184 +1355,4 @@ async function getDiskPath() { } } -function createModifierCard(name, previews) { - const modifierCard = document.createElement('div') - modifierCard.className = 'modifier-card' - modifierCard.innerHTML = ` -
-
-
+
-

- Modifier Image -
-
-

-
` - - 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[0]; // portrait - image.setAttribute('preview-type', 'portrait') - } else { - image.remove() - } - - const maxLabelLength = 30 - const nameWithoutBy = name.replace('by ', '') - - if(nameWithoutBy.length <= maxLabelLength) { - label.querySelector('p').innerText = nameWithoutBy - } else { - const tooltipText = document.createElement('span') - tooltipText.className = 'tooltip-text' - tooltipText.innerText = name - - label.classList.add('tooltip') - label.appendChild(tooltipText) - - label.querySelector('p').innerText = nameWithoutBy.substring(0, maxLabelLength) + '...' - } - - return modifierCard -} - -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)) - } - }) -} - -async function loadModifiers() { - try { - let res = await fetch('/get/modifiers') - if (res.status === 200) { - res = await res.json() - - modifiers = res; // update global variable - - res.forEach((modifierGroup, idx) => { - 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 (idx == 0) { - titleEl.className += ' active' - modifiersEl.style.display = 'block' - } - - modifiers.forEach(modObj => { - const modifierName = modObj.modifier - const modifierPreviews = modObj?.previews?.map(preview => `${modifierThumbnailPath}/${preview.path}`) - - const modifierCard = createModifierCard(modifierName, modifierPreviews) - - if(typeof modifierCard == 'object') { - modifiersEl.appendChild(modifierCard) - - modifierCard.addEventListener('click', () => { - if (activeTags.map(x => x.name).includes(modifierName)) { - // remove modifier from active array - activeTags = activeTags.filter(x => x.name != modifierName) - modifierCard.classList.remove(activeCardClass) - - modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+' - } else { - // add modifier to active array - activeTags.push({ - 'name': modifierName, - 'element': modifierCard.cloneNode(true), - 'originElement': modifierCard, - 'previews': modifierPreviews - }) - - modifierCard.classList.add(activeCardClass) - - modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-' - } - - refreshTagsList() - }) - } - }) - - let brk = document.createElement('br') - brk.style.clear = 'both' - modifiersEl.appendChild(brk) - - let e = document.createElement('div') - e.appendChild(titleEl) - e.appendChild(modifiersEl) - - editorModifierEntries.appendChild(e) - }) - - createCollapsibles(editorModifierEntries) - } - } catch (e) { - console.log('error fetching modifiers', e) - } -} +createCollapsibles() \ No newline at end of file diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js new file mode 100644 index 00000000..05c6d4ab --- /dev/null +++ b/ui/media/js/utils.js @@ -0,0 +1,103 @@ +// https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/ +function getNextSibling(elem, selector) { + // Get the next sibling element + var sibling = elem.nextElementSibling + + // If there's no selector, return the first sibling + if (!selector) return sibling + + // If the sibling matches our selector, use it + // If not, jump to the next sibling and continue the loop + while (sibling) { + if (sibling.matches(selector)) return sibling + sibling = sibling.nextElementSibling + } +} + +function createCollapsibles(node) { + if (!node) { + node = document + } + + let collapsibles = node.querySelectorAll(".collapsible") + collapsibles.forEach(function(c) { + let handle = document.createElement('span') + handle.className = 'collapsible-handle' + + if (c.className.indexOf('active') !== -1) { + handle.innerHTML = '➖' // minus + } else { + handle.innerHTML = '➕' // plus + } + c.insertBefore(handle, c.firstChild) + + c.addEventListener('click', function() { + this.classList.toggle("active") + let content = getNextSibling(this, '.collapsible-content') + if (content.style.display === "block") { + content.style.display = "none" + handle.innerHTML = '➕' // plus + } else { + content.style.display = "block" + handle.innerHTML = '➖' // minus + } + + if (this == advancedPanelHandle) { + let state = (content.style.display === 'block' ? 'true' : 'false') + localStorage.setItem(ADVANCED_PANEL_OPEN_KEY, state) + } else if (this == modifiersPanelHandle) { + let state = (content.style.display === 'block' ? 'true' : 'false') + localStorage.setItem(MODIFIERS_PANEL_OPEN_KEY, state) + } else if (this == negativePromptPanelHandle) { + let state = (content.style.display === 'block' ? 'true' : 'false') + localStorage.setItem(NEGATIVE_PROMPT_PANEL_OPEN_KEY, state) + } + }) + }) +} + +function permute(arr) { + let permutations = [] + let n = arr.length + 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') + + for (let idx = 0; idx < mask.length; idx++) { + if (mask[idx] === '1' && arr[idx].trim() !== '') { + perm.push(arr[idx]) + } + } + + if (perm.length > 0) { + permutations.push(perm) + } + } + + return permutations +} + +// https://stackoverflow.com/a/8212878 +function millisecondsToStr(milliseconds) { + function numberEnding (number) { + return (number > 1) ? 's' : '' + } + + var temp = Math.floor(milliseconds / 1000) + var hours = Math.floor((temp %= 86400) / 3600) + var s = '' + if (hours) { + s += hours + ' hour' + numberEnding(hours) + ' ' + } + var minutes = Math.floor((temp %= 3600) / 60) + if (minutes) { + s += minutes + ' minute' + numberEnding(minutes) + ' ' + } + var seconds = temp % 60 + if (!hours && minutes < 4 && seconds) { + s += seconds + ' second' + numberEnding(seconds) + } + + return s +} \ No newline at end of file