diff --git a/CHANGELOG.md b/CHANGELOG.md index 829345b53..3d50229b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ any ui state including state of open/closed menus can be saved as default! see *System -> User interface -> Set menu states* - **Extra networks**: + - new details interface to view and save data about extra networks + main ui now has a single button on each en to trigger details view - faster search, ability to show/hide/sort networks - refactored subfolder handling *note*: this will trigger model hash recaclulation on first model use diff --git a/html/locale_en.json b/html/locale_en.json index 16ddb76f5..1e8efe127 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -94,6 +94,7 @@ {"id":"","label":"save","localized":"","hint":"Save image"}, {"id":"","label":"zip","localized":"","hint":"Create zip archive from images"}, {"id":"","label":"delete","localized":"","hint":"Delete image"}, + {"id":"","label":"replace","localized":"","hint":"Replace image"}, {"id":"","label":"➠ text","localized":"","hint":"Transfer image to text interface"}, {"id":"","label":"➠ image","localized":"","hint":"Transfer image to image interface"}, {"id":"","label":"➠ inpaint","localized":"","hint":"Transfer image to inpaint interface"}, diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 1b6de2316..0a61074f2 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -1,34 +1,42 @@ -let globalPopup = null; -let globalPopupInner = null; -let previousCard = null; const activePromptTextarea = {}; -const re_extranet = /<([^:]+:[^:]+):[\d\.]+>/; -const re_extranet_g = /\s+<([^:]+:[^:]+):[\d\.]+>/g; -const getENActiveTab = () => gradioApp().getElementById('tab_txt2img').style.display === 'block' ? 'txt2img' : 'img2img'; +// helpers -function requestGet(url, data, handler, errorHandler) { +const requestGet = (url, data, handler) => { const xhr = new XMLHttpRequest(); const args = Object.keys(data).map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`).join('&'); xhr.open('GET', `${url}?${args}`, true); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { - if (xhr.status === 200) { - try { - const js = JSON.parse(xhr.responseText); - handler(js); - } catch (error) { - console.error(error); - errorHandler(); - } - } else { - errorHandler(); - } + if (xhr.status === 200) handler(JSON.parse(xhr.responseText)); + else console.error(`Request: url=${url} status=${xhr.status} err`); } }; - const js = JSON.stringify(data); - xhr.send(js); -} + xhr.send(JSON.stringify(data)); +}; + +const getENActiveTab = () => gradioApp().getElementById('tab_txt2img').style.display === 'block' ? 'txt2img' : 'img2img'; + +const getENActivePage = () => { + const tabname = getENActiveTab(); + const page = gradioApp().querySelector(`#${tabname}_extra_networks > .tabs > .tab-nav > .selected`); + return page ? page.innerText : ''; +}; + +const setENState = (state) => { + if (!state) return; + state.tab = getENActiveTab(); + state.page = getENActivePage(); + log('setENState', state); + const el = gradioApp().querySelector(`#${state.tab}_extra_state > label > textarea`); + el.value = JSON.stringify(state); + updateInput(el); +}; + +// popup + +let globalPopup = null; +let globalPopupInner = null; function popup(contents) { if (!globalPopup) { @@ -51,6 +59,45 @@ function popup(contents) { globalPopup.style.display = 'flex'; } +// methods + +function showCardDetails(event) { + const tabname = getENActiveTab(); + setENState({ op: 'showCardDetails' }); + const btn = gradioApp().getElementById(`${tabname}_extra_details_btn`); + btn.click(); + event.stopPropagation(); + event.preventDefault(); +} + +function getCardDetails(...args) { + const el = event?.target?.parentElement?.parentElement?.parentElement; + if (!el?.classList?.contains('card')) return [...args]; + const tabname = getENActiveTab(); + setENState({ op: 'getCardDetails', item: el.dataset.name }); + return [...args]; +} + +function saveCardDescription(event) { + const el = event?.target?.parentElement?.parentElement?.parentElement; + if (!el?.classList?.contains('card')) return; + const tabname = getENActiveTab(); + const description = gradioApp().getElementById(`${tabname}_description`)?.children[0].children[1].value; + el.dataset.description = description; + setENState({ op: 'saveCardDescription', item: el.dataset.name, description }); + event.stopPropagation(); + event.preventDefault(); +} + +function saveCardPreview(event) { + const el = event?.target?.parentElement?.parentElement?.parentElement; + if (!el?.classList?.contains('card')) return; + const index = selected_gallery_index(); + setENState({ op: 'saveCardPreview', item: el.dataset.name, index }); + event.stopPropagation(); + event.preventDefault(); +} + function readCardMetadata(event, extraPage, cardName) { requestGet('./sd_extra_networks/metadata', { page: extraPage, item: cardName }, (data) => { if (data?.metadata) { @@ -60,7 +107,7 @@ function readCardMetadata(event, extraPage, cardName) { elem.textContent = data.metadata; popup(elem); } - }, () => { }); + }); event.stopPropagation(); event.preventDefault(); } @@ -88,54 +135,14 @@ function readCardTags(el, tags) { } } -function readCardInformation(event, extraPage, cardName) { - requestGet('./sd_extra_networks/info', { page: extraPage, item: cardName }, (data) => { - if (data?.info && (typeof (data?.info) === 'string')) { - const elem = document.createElement('pre'); - elem.classList.add('popup-metadata'); - elem.textContent = data.info; - popup(elem); - } - }, () => { }); - event.stopPropagation(); - event.preventDefault(); -} - function readCardDescription(filename, descript) { const tabname = getENActiveTab(); - const fn = gradioApp().querySelector(`#${tabname}_description_filename > label > textarea`); const description = gradioApp().querySelector(`#${tabname}_description > label > textarea`); - fn.value = filename; description.value = descript?.trim() || ''; description.focus(); - updateInput(fn); updateInput(description); } -function saveCardPreview(event) { - const tabname = getENActiveTab(); - const textarea = gradioApp().querySelector(`#${tabname}_preview_filename > label > textarea`); - const button = gradioApp().getElementById(`${tabname}_save_preview`); - const el = event?.target?.parentElement?.parentElement?.parentElement; - if (el?.classList?.contains('card')) { - textarea.value = el.dataset.filename; - updateInput(textarea); - button.click(); - } - event.stopPropagation(); - event.preventDefault(); -} - -function saveCardDescription(event) { - const tabname = getENActiveTab(); - const el = event?.target?.parentElement?.parentElement?.parentElement; - if (el?.classList?.contains('card')) el.dataset.description = gradioApp().getElementById(`${tabname}_description`)?.children[0].children[1].value; - const button = gradioApp().getElementById(`${tabname}_save_description`); - button.click(); - event.stopPropagation(); - event.preventDefault(); -} - async function filterExtraNetworksForTab(tabname, searchTerm) { let found = 0; let items = 0; @@ -161,6 +168,79 @@ async function filterExtraNetworksForTab(tabname, searchTerm) { else log(`filterExtraNetworks: text=all items=${items} time=${Math.round(1000 * (t1 - t0)) / 1000000}`); } +function tryToRemoveExtraNetworkFromPrompt(textarea, text) { + const re_extranet = /<([^:]+:[^:]+):[\d\.]+>/; + const re_extranet_g = /\s+<([^:]+:[^:]+):[\d\.]+>/g; + let m = text.match(re_extranet); + let replaced = false; + let newTextareaText; + if (m) { + const partToSearch = m[1]; + newTextareaText = textarea.value.replaceAll(re_extranet_g, (found) => { + m = found.match(re_extranet); + if (m[1] === partToSearch) { + replaced = true; + return ''; + } + return found; + }); + } else { + newTextareaText = textarea.value.replaceAll(new RegExp(text, 'g'), (found) => { + if (found === text) { + replaced = true; + return ''; + } + return found; + }); + } + if (replaced) { + textarea.value = newTextareaText; + return true; + } + return false; +} + +function refreshExtraNetworks(tabname) { + log('refreshExtraNetworks', tabname, gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.value); + gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.dispatchEvent(new Event('input')); +} + +function cardClicked(textToAdd, allowNegativePrompt) { + const tabname = getENActiveTab(); + const textarea = allowNegativePrompt ? activePromptTextarea[tabname] : gradioApp().querySelector(`#${tabname}_prompt > label > textarea`); + if (textarea.value.indexOf(textToAdd) !== -1) textarea.value = textarea.value.replace(textToAdd, ''); + else textarea.value += textToAdd; + updateInput(textarea); +} + +function extraNetworksSearchButton(event) { + const tabname = getENActiveTab(); + const searchTextarea = gradioApp().querySelector(`#${tabname}_extra_tabs > div > div > textarea`); + const button = event.target; + const text = button.classList.contains('search-all') ? '' : `${button.textContent.trim()}/`; + searchTextarea.value = text; + updateInput(searchTextarea); +} + +let desiredStyle = ''; +function selectStyle(name) { + desiredStyle = name; + const tabname = getENActiveTab(); + const button = gradioApp().querySelector(`#${tabname}_styles_select`); + button.click(); +} + +function applyStyles(styles) { + let newStyles = []; + if (styles) newStyles = Array.isArray(styles) ? styles : [styles]; + const index = newStyles.indexOf(desiredStyle); + if (index > -1) newStyles.splice(index, 1); + else newStyles.push(desiredStyle); + return newStyles.join('|'); +} + +// init + function setupExtraNetworksForTab(tabname) { gradioApp().querySelector(`#${tabname}_extra_tabs`).classList.add('extra-networks'); const tabs = gradioApp().querySelector(`#${tabname}_extra_tabs > div`); @@ -188,6 +268,7 @@ function setupExtraNetworksForTab(tabname) { }); let hoverTimer = null; + let previousCard = null; gradioApp().getElementById(`${tabname}_extra_tabs`).onmouseover = (e) => { const el = e.target.closest('.card'); // bubble-up to card if (!el || (el.title === previousCard)) return; @@ -261,79 +342,4 @@ function setupExtraNetworks() { log('initExtraNetworks'); } -function tryToRemoveExtraNetworkFromPrompt(textarea, text) { - let m = text.match(re_extranet); - let replaced = false; - let newTextareaText; - if (m) { - const partToSearch = m[1]; - newTextareaText = textarea.value.replaceAll(re_extranet_g, (found) => { - m = found.match(re_extranet); - if (m[1] === partToSearch) { - replaced = true; - return ''; - } - return found; - }); - } else { - newTextareaText = textarea.value.replaceAll(new RegExp(text, 'g'), (found) => { - if (found === text) { - replaced = true; - return ''; - } - return found; - }); - } - if (replaced) { - textarea.value = newTextareaText; - return true; - } - return false; -} - -function refreshExtraNetworks(tabname) { - console.log('refreshExtraNetworks', tabname, gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.value); - gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.dispatchEvent(new Event('input')); -} - -function cardClicked(textToAdd, allowNegativePrompt) { - const tabname = getENActiveTab(); - const textarea = allowNegativePrompt ? activePromptTextarea[tabname] : gradioApp().querySelector(`#${tabname}_prompt > label > textarea`); - if (textarea.value.indexOf(textToAdd) !== -1) textarea.value = textarea.value.replace(textToAdd, ''); - else textarea.value += textToAdd; - updateInput(textarea); -} - -function extraNetworksSearchButton(event) { - const tabname = getENActiveTab(); - const searchTextarea = gradioApp().querySelector(`#${tabname}_extra_tabs > div > div > textarea`); - const button = event.target; - const text = button.classList.contains('search-all') ? '' : `${button.textContent.trim()}/`; - searchTextarea.value = text; - updateInput(searchTextarea); -} - -function getENActivePage() { - const tabname = getENActiveTab(); - const page = gradioApp().querySelector(`#${tabname}_extra_networks > .tabs > .tab-nav > .selected`); - return page ? page.innerText : ''; -} - -let desiredStyle = ''; -function selectStyle(name) { - desiredStyle = name; - const tabname = getENActiveTab(); - const button = gradioApp().querySelector(`#${tabname}_styles_select`); - button.click(); -} - -function applyStyles(styles) { - let newStyles = []; - if (styles) newStyles = Array.isArray(styles) ? styles : [styles]; - const index = newStyles.indexOf(desiredStyle); - if (index > -1) newStyles.splice(index, 1); - else newStyles.push(desiredStyle); - return newStyles.join('|'); -} - onUiLoaded(setupExtraNetworks); diff --git a/javascript/light-teal.css b/javascript/light-teal.css index 2e2779b47..20acfb9bc 100644 --- a/javascript/light-teal.css +++ b/javascript/light-teal.css @@ -38,7 +38,7 @@ html { font-size: var(--font-size); } body, button, input, select, textarea { font-family: var(--font);} -button { font-size: 1.2rem; max-width: 400px; } +button { font-size: 1.2rem; max-width: 400px; --button-large-radius: var(--radius-lg) } img { background-color: var(--background-color); } input[type=range] { height: var(--line-sm); appearance: none; margin-top: 0; min-width: 160px; background-color: var(--background-color); width: 100%; background: transparent; } input[type=range]::-webkit-slider-runnable-track { width: 100%; height: var(--line-sm); cursor: pointer; box-shadow: 2px 2px 3px #111111; background: var(--input-background-fill); border-radius: var(--radius-lg); border: 0px solid #222222; } @@ -128,137 +128,27 @@ svg.feather.feather-image, .feather .feather-image { display: none } /* based on gradio built-in dark theme */ :root, .light, .dark { - --body-background-fill: var(--background-color); - --color-accent-soft: var(--neutral-700); --background-fill-secondary: none; - --border-color-accent: var(--background-color); - --border-color-primary: var(--background-color); - --link-text-color-active: var(--secondary-500); - --link-text-color: var(--secondary-500); - --link-text-color-hover: var(--secondary-400); - --link-text-color-visited: var(--secondary-600); - --shadow-spread: 1px; - --block-background-fill: None; - --block-border-color: var(--border-color-primary); --block_border_width: None; - --block-info-text-color: var(--body-text-color-subdued); - --block-label-background-fill: var(--background-fill-secondary); - --block-label-border-color: var(--border-color-primary); --block_label_border_width: None; - --block-label-text-color: var(--neutral-200); --block_shadow: None; --block_title_background_fill: None; --block_title_border_color: None; --block_title_border_width: None; - --panel-background-fill: var(--background-fill-secondary); - --panel-border-color: var(--border-color-primary); - --panel_border_width: None; - --checkbox-background-color: var(--neutral-500); - --checkbox-background-color-focus: var(--checkbox-background-color); - --checkbox-background-color-hover: var(--checkbox-background-color); - --checkbox-background-color-selected: var(--primary-500); - --checkbox-border-color: transparent; - --checkbox-border-color-focus: var(--secondary-500); - --checkbox-border-color-hover: var(--neutral-600); - --checkbox-border-color-selected: var(--primary-600); - --checkbox-border-width: var(--input-border-width); - --checkbox-label-background-fill: None; - --checkbox-label-background-fill-hover: None; - --checkbox-label-background-fill-selected: var(--checkbox-label-background-fill); - --checkbox-label-border-color: var(--border-color-primary); - --checkbox-label-border-color-hover: var(--checkbox-label-border-color); - --checkbox-label-border-width: var(--input-border-width); - --checkbox-label-text-color: var(--body-text-color); - --checkbox-label-text-color-selected: var(--checkbox-label-text-color); - --error-background-fill: var(--background-fill-primary); - --error-border-color: var(--border-color-primary); - --error_border_width: None; - --error-text-color: #ef4444; - --input-background-fill-focus: var(--secondary-600); - --input-background-fill-hover: var(--input-background-fill); - --input-border-color: var(--border-color-primary); - --input-border-color-focus: var(--neutral-700); - --input-border-color-hover: var(--input-border-color); - --input_border_width: None; - --input-placeholder-color: var(--neutral-500); - --input-shadow-focus: 2px 2px 2px 2px #111111; - --loader_color: None; - --slider_color: None; - --stat-background-fill: linear-gradient(to right, var(--primary-400), var(--primary-600)); - --table-border-color: var(--neutral-700); - --table-even-background-fill: #222222; - --table-odd-background-fill: #333333; - --table-row-focus: var(--color-accent-soft); - --button-border-width: var(--input-border-width); - --button-cancel-background-fill: linear-gradient(to bottom right, #dc2626, #b91c1c); - --button-cancel-background-fill-hover: linear-gradient(to bottom right, #dc2626, #dc2626); - --button-cancel-border-color: #dc2626; - --button-cancel-border-color-hover: var(--button-cancel-border-color); - --button-cancel-text-color: white; - --button-cancel-text-color-hover: var(--button-cancel-text-color); - --button-primary-background-fill: linear-gradient(to bottom right, var(--primary-500), var(--primary-800)); - --button-primary-background-fill-hover: linear-gradient(to bottom right, var(--primary-500), var(--primary-300)); - --button-primary-border-color: var(--primary-500); - --button-primary-border-color-hover: var(--button-primary-border-color); - --button-primary-text-color: white; - --button-primary-text-color-hover: var(--button-primary-text-color); - --button-secondary-border-color: var(--neutral-600); - --button-secondary-border-color-hover: var(--button-secondary-border-color); - --button-secondary-text-color-hover: var(--button-secondary-text-color); - --secondary-50: #eff6ff; - --secondary-100: #dbeafe; - --secondary-200: #bfdbfe; - --secondary-300: #93c5fd; - --secondary-400: #60a5fa; - --secondary-500: #3b82f6; - --secondary-600: #2563eb; - --secondary-700: #1d4ed8; - --secondary-800: #1e40af; - --secondary-900: #1e3a8a; - --secondary-950: #1d3660; - --neutral-50: #f0f0f0; - --neutral-100: #e0e0e0; - --neutral-200: #d0d0d0; - --neutral-300: #b0b0b0; - --neutral-400: #909090; - --neutral-500: #707070; - --neutral-600: #606060; - --neutral-700: #404040; - --neutral-800: #333333; - --neutral-900: #111827; - --neutral-950: #0b0f19; - --spacing-xxs: 1px; - --spacing-xs: 2px; - --spacing-sm: 4px; - --spacing-lg: 8px; - --spacing-xl: 10px; - --radius-xxs: 0; - --radius-xs: 0; - --radius-md: 0; - --radius-xl: 0; - --radius-xxl: 0; - --text-xxs: 9px; - --text-xs: 10px; - --text-sm: 12px; - --text-md: 14px; - --text-lg: 16px; - --text-xl: 22px; - --text-xxl: 26px; - --body-text-size: var(--text-md); - --body-text-weight: 400; - --embed-radius: var(--radius-lg); - --color-accent: var(--primary-500); - --shadow-drop: 0; - --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); - --shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset; + --block-background-fill: None; + --block-border-color: var(--border-color-primary); --block-border-width: 1px; + --block-info-text-color: var(--body-text-color-subdued); --block-info-text-size: var(--text-sm); --block-info-text-weight: 400; + --block-label-background-fill: var(--background-fill-secondary); + --block-label-border-color: var(--border-color-primary); --block-label-border-width: 1px; --block-label-margin: 0; --block-label-padding: var(--spacing-sm) var(--spacing-lg); --block-label-radius: calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px) 0; --block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px); + --block-label-text-color: var(--neutral-200); --block-label-text-size: var(--text-sm); --block-label-text-weight: 400; --block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px); @@ -271,44 +161,154 @@ svg.feather.feather-image, .feather .feather-image { display: none } --block-title-radius: none; --block-title-text-size: var(--text-md); --block-title-text-weight: 400; - --container-radius: var(--radius-lg); - --form-gap-width: 1px; - --layout-gap: var(--spacing-xxl); - --panel-border-width: 0; - --section-header-text-size: var(--text-md); - --section-header-text-weight: 400; - --checkbox-border-radius: var(--radius-sm); - --checkbox-label-gap: 2px; - --checkbox-label-padding: var(--spacing-md); - --checkbox-label-shadow: var(--shadow-drop); - --checkbox-label-text-size: var(--text-md); - --checkbox-label-text-weight: 400; - --checkbox-check: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); - --radio-circle: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); - --checkbox-shadow: var(--input-shadow); - --error-border-width: 1px; - --input-border-width: 0; - --input-radius: var(--radius-lg); - --input-text-size: var(--text-md); - --input-text-weight: 400; - --loader-color: var(--color-accent); - --prose-text-size: var(--text-md); - --prose-text-weight: 400; - --prose-header-text-weight: 600; - --slider-color: ; - --table-radius: var(--radius-lg); + --body-background-fill: var(--background-color); + --body-text-size: var(--text-md); + --body-text-weight: 400; + --border-color-accent: var(--background-color); + --border-color-primary: var(--background-color); + --button-border-width: var(--input-border-width); + --button-cancel-background-fill-hover: linear-gradient(to bottom right, #dc2626, #dc2626); + --button-cancel-background-fill: linear-gradient(to bottom right, #dc2626, #b91c1c); + --button-cancel-border-color-hover: var(--button-cancel-border-color); + --button-cancel-border-color: #dc2626; + --button-cancel-text-color-hover: var(--button-cancel-text-color); + --button-cancel-text-color: white; --button-large-padding: 2px 10px; --button-large-radius: var(--radius-lg); --button-large-text-size: var(--text-lg); --button-large-text-weight: 400; - --button-shadow: 4px 4px 4px 0px #333333; + --button-primary-background-fill-hover: linear-gradient(to bottom right, var(--primary-500), var(--primary-300)); + --button-primary-background-fill: linear-gradient(to bottom right, var(--primary-500), var(--primary-800)); + --button-primary-border-color-hover: var(--button-primary-border-color); + --button-primary-border-color: var(--primary-500); + --button-primary-text-color-hover: var(--button-primary-text-color); + --button-primary-text-color: white; + --button-secondary-border-color-hover: var(--button-secondary-border-color); + --button-secondary-border-color: var(--neutral-600); + --button-secondary-text-color-hover: var(--button-secondary-text-color); --button-shadow-active: 1px 1px 4px 0px #555555; --button-shadow-hover: 1px 1px 4px 0px #555555; + --button-shadow: 4px 4px 4px 0px #333333; --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm)); --button-small-radius: var(--radius-lg); --button-small-text-size: var(--text-md); --button-small-text-weight: 400; --button-transition: none; - --size-9: 64px; + --checkbox-background-color-focus: var(--checkbox-background-color); + --checkbox-background-color-hover: var(--checkbox-background-color); + --checkbox-background-color-selected: var(--primary-500); + --checkbox-background-color: var(--neutral-500); + --checkbox-border-color-focus: var(--secondary-500); + --checkbox-border-color-hover: var(--neutral-600); + --checkbox-border-color-selected: var(--primary-600); + --checkbox-border-color: transparent; + --checkbox-border-radius: var(--radius-sm); + --checkbox-border-width: var(--input-border-width); + --checkbox-check: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); + --checkbox-label-background-fill-hover: None; + --checkbox-label-background-fill-selected: var(--checkbox-label-background-fill); + --checkbox-label-background-fill: None; + --checkbox-label-border-color-hover: var(--checkbox-label-border-color); + --checkbox-label-border-color: var(--border-color-primary); + --checkbox-label-border-width: var(--input-border-width); + --checkbox-label-gap: 2px; + --checkbox-label-padding: var(--spacing-md); + --checkbox-label-shadow: var(--shadow-drop); + --checkbox-label-text-color-selected: var(--checkbox-label-text-color); + --checkbox-label-text-color: var(--body-text-color); + --checkbox-label-text-size: var(--text-md); + --checkbox-label-text-weight: 400; + --checkbox-shadow: var(--input-shadow); + --color-accent-soft: var(--neutral-700); + --color-accent: var(--primary-500); + --container-radius: var(--radius-lg); + --embed-radius: var(--radius-lg); + --error_border_width: None; + --error-background-fill: var(--background-fill-primary); + --error-border-color: var(--border-color-primary); + --error-border-width: 1px; + --error-text-color: #ef4444; + --form-gap-width: 1px; + --input_border_width: None; + --input-background-fill-focus: var(--secondary-600); + --input-background-fill-hover: var(--input-background-fill); + --input-border-color-focus: var(--neutral-700); + --input-border-color-hover: var(--input-border-color); + --input-border-color: var(--border-color-primary); + --input-border-width: 0; + --input-placeholder-color: var(--neutral-500); + --input-radius: var(--radius-lg); + --input-shadow-focus: 2px 2px 2px 2px #111111; + --input-text-size: var(--text-md); + --input-text-weight: 400; + --layout-gap: var(--spacing-xxl); + --link-text-color-active: var(--secondary-500); + --link-text-color-hover: var(--secondary-400); + --link-text-color-visited: var(--secondary-600); + --link-text-color: var(--secondary-500); + --loader_color: None; + --loader-color: var(--color-accent); + --neutral-100: #e0e0e0; + --neutral-200: #d0d0d0; + --neutral-300: #b0b0b0; + --neutral-400: #909090; + --neutral-50: #f0f0f0; + --neutral-500: #707070; + --neutral-600: #606060; + --neutral-700: #404040; + --neutral-800: #333333; + --neutral-900: #111827; + --neutral-950: #0b0f19; + --panel_border_width: None; + --panel-background-fill: var(--background-fill-secondary); + --panel-border-color: var(--border-color-primary); + --panel-border-width: 0; + --prose-header-text-weight: 600; + --prose-text-size: var(--text-md); + --prose-text-weight: 400; + --radio-circle: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); + --radius-md: 0; + --radius-xl: 0; + --radius-xs: 0; + --radius-xxl: 0; + --radius-xxs: 0; + --secondary-100: #dbeafe; + --secondary-200: #bfdbfe; + --secondary-300: #93c5fd; + --secondary-400: #60a5fa; + --secondary-50: #eff6ff; + --secondary-500: #3b82f6; + --secondary-600: #2563eb; + --secondary-700: #1d4ed8; + --secondary-800: #1e40af; + --secondary-900: #1e3a8a; + --secondary-950: #1d3660; + --section-header-text-size: var(--text-md); + --section-header-text-weight: 400; + --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-drop: 0; + --shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset; + --shadow-spread: 1px; --size-14: 64px; + --size-9: 64px; + --slider_color: None; + --slider-color: ; + --spacing-lg: 8px; + --spacing-sm: 4px; + --spacing-xl: 10px; + --spacing-xs: 2px; + --spacing-xxs: 1px; + --stat-background-fill: linear-gradient(to right, var(--primary-400), var(--primary-600)); + --table-border-color: var(--neutral-700); + --table-even-background-fill: #222222; + --table-odd-background-fill: #333333; + --table-radius: var(--radius-lg); + --table-row-focus: var(--color-accent-soft); + --text-lg: 16px; + --text-md: 14px; + --text-sm: 12px; + --text-xl: 22px; + --text-xs: 10px; + --text-xxl: 26px; + --text-xxs: 9px; } diff --git a/javascript/style.css b/javascript/style.css index 31dfdf32d..58aa2de8d 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -1,7 +1,6 @@ :root, .dark{ --checkbox-label-gap: 0.25em 0.1em; --section-header-text-size: 12pt; --block-background-fill: transparent;} a { font-weight: bold; cursor: pointer; } h2 { margin-top: 1em !important; font-size: 1.4em !important; } -table { overflow-x: auto !important; overflow-y: auto !important; } div.gradio-container{ max-width: unset !important; padding: 8px !important; } div.tabitem { padding: 0 !important; } div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; gap: 0.5em 1em; flex-grow: 1 !important; } @@ -35,7 +34,9 @@ div.gradio-html.min{ min-height: 0; } .small-accordion .label-wrap .icon { margin-right: 1.6em; margin-left: 0.6em; color: var(--button-primary-border-color); } .hidden { display: none; } footer { display: none; } -td { border-bottom: none !important; } +table { overflow-x: auto !important; overflow-y: auto !important; } +td { border-bottom: none !important; padding: 0.1em 0.5em !important; } +tr { border-bottom: none !important; padding: 0.1em 0.5em !important; } /* general styled components */ .gradio-button.tool { max-width: min-content; min-width: min-content !important; align-self: end; font-size: 1.4em; color: var(--body-text-color) !important; } @@ -248,39 +249,31 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt .extra-network-cards .card .preview { box-shadow: var(--button-shadow); min-height: 30px; } .extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); } .extra-network-cards .card:hover .preview { box-shadow: none; filter: grayscale(100%); } +.extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); } +.extra-network-cards .card .overlay .tags { margin: 4px; display: none; overflow-wrap: break-word; } +.extra-network-cards .card .overlay .tag { padding: 2px; margin: 2px; background: var(--neutral-700); cursor: pointer; display: inline-block; } +.extra-network-cards .card .overlay .actions { font-size: 2.2em; display: none; text-align-last: center; cursor: pointer; font-variant: unicase; height: 0.8em } +.extra-network-cards .card .overlay .actions > span { padding: 4px; } +.extra-network-cards .card .overlay .description { line-break: anywhere; display: none; color: white; } +.extra-network-cards .card:hover .overlay .actions { display: block; } +.extra-network-cards .card:hover .overlay .tags { display: block; } +.extra-network-cards .card:hover .overlay .description { display: block; } + #txt2img_description, #img2img_description { max-height: 63px; overflow-y: auto !important; } #txt2img_description > label > textarea, #img2img_description > label > textarea { font-size: 0.9em } - -.extra-network-cards .card .info { - position: absolute; - font-size: 1.5em; - top: 0.1em; - right: 0.1em; - background: var(--button-primary-border-color); - width: 1.3em; - text-align: center; - border-radius: var(--block-radius); - height: 1.3em; - line-height: 1.2em; - cursor: pointer; - visibility: hidden; - z-index: 100; -} -.extra-network-cards .card:hover .info { visibility: visible; } - -.extra-networks_details { +#txt2img_extra_details, #img2img_extra_details { position: fixed; bottom: 50%; left: 50%; transform: translate(-50%, 50%); - padding: 1em; + padding: 0.8em; border: var(--block-border-width) solid var(--highlight-color) !important; z-index: 100; box-shadow: var(--button-shadow); - width: 80vw; - display: none !important; } +#txt2img_extra_details > div, #img2img_extra_details > div { overflow-y: auto; min-height: 40vh; max-height: 80vh; align-self: flex-start; } +#txt2img_extra_details td:first-child, #img2img_extra_details td:first-child { font-weight: bold; } /* controlnet .controlnet_control_type .controlnet_control_type_filter_group .wrap:last-of-type { display: grid; grid-auto-flow: row; grid-template-columns: repeat(4, minmax(0, 1fr)); } diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 625e7dd1e..2013d17ee 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -5,13 +5,14 @@ import html import os.path import urllib.parse import threading +from datetime import datetime +from types import SimpleNamespace from pathlib import Path from collections import OrderedDict import gradio as gr from PIL import Image from starlette.responses import FileResponse, JSONResponse from modules import shared, scripts, modelloader -from modules.generation_parameters_copypaste import image_from_url_text from modules.ui_components import ToolButton import modules.ui_symbols as symbols @@ -28,10 +29,7 @@ def listdir(path): if path in dir_cache and os.path.getmtime(path) == dir_cache[path][0]: return dir_cache[path][1] else: - dir_cache[path] = ( - os.path.getmtime(path), - [os.path.join(path, f) for f in os.listdir(path)] - ) + dir_cache[path] = (os.path.getmtime(path), [os.path.join(path, f) for f in os.listdir(path)]) return dir_cache[path][1] @@ -45,41 +43,39 @@ def register_page(page): allowed_dirs.append(os.path.abspath(folder)) -def fetch_file(filename: str = ""): - if not os.path.exists(filename): - return JSONResponse({ "error": f"file {filename}: not found" }, status_code=404) - if filename.startswith('html/') or filename.startswith('models/'): +def init_api(app): + def fetch_file(filename: str = ""): + if not os.path.exists(filename): + return JSONResponse({ "error": f"file {filename}: not found" }, status_code=404) + if filename.startswith('html/') or filename.startswith('models/'): + return FileResponse(filename, headers={"Accept-Ranges": "bytes"}) + if not any(Path(folder).absolute() in Path(filename).absolute().parents for folder in allowed_dirs): + return JSONResponse({ "error": f"file {filename}: must be in one of allowed directories" }, status_code=403) + if os.path.splitext(filename)[1].lower() not in (".png", ".jpg", ".jpeg", ".webp"): + return JSONResponse({"error": f"file {filename}: not an image file"}, status_code=403) return FileResponse(filename, headers={"Accept-Ranges": "bytes"}) - if not any(Path(folder).absolute() in Path(filename).absolute().parents for folder in allowed_dirs): - return JSONResponse({ "error": f"file {filename}: must be in one of allowed directories" }, status_code=403) - if os.path.splitext(filename)[1].lower() not in (".png", ".jpg", ".jpeg", ".webp"): - return JSONResponse({"error": f"file {filename}: not an image file"}, status_code=403) - return FileResponse(filename, headers={"Accept-Ranges": "bytes"}) + + def get_metadata(page: str = "", item: str = ""): + page = next(iter([x for x in shared.extra_networks if x.name == page]), None) + if page is None: + return JSONResponse({ 'metadata': 'none' }) + metadata = page.metadata.get(item, 'none') + if metadata is None: + metadata = '' + shared.log.debug(f"Extra networks metadata: page='{page}' item={item} len={len(metadata)}") + return JSONResponse({"metadata": metadata}) -def get_metadata(page: str = "", item: str = ""): - page = next(iter([x for x in shared.extra_networks if x.name == page]), None) - if page is None: - return JSONResponse({ 'metadata': 'none' }) - metadata = page.metadata.get(item, 'none') - if metadata is None: - metadata = '' - shared.log.debug(f"Extra networks metadata: page='{page}' item={item} len={len(metadata)}") - return JSONResponse({"metadata": metadata}) + def get_info(page: str = "", item: str = ""): + page = next(iter([x for x in shared.extra_networks if x.name == page]), None) + if page is None: + return JSONResponse({ 'info': 'none' }) + info = page.info.get(item, 'none') + if info is None: + info = '' + shared.log.debug(f"Extra networks info: page='{page}' item={item} len={len(info)}") + return JSONResponse({"info": info}) - -def get_info(page: str = "", item: str = ""): - page = next(iter([x for x in shared.extra_networks if x.name == page]), None) - if page is None: - return JSONResponse({ 'info': 'none' }) - info = page.info.get(item, 'none') - if info is None: - info = '' - shared.log.debug(f"Extra networks info: page='{page}' item={item} len={len(info)}") - return JSONResponse({"info": info}) - - -def add_pages_to_demo(app): app.add_api_route("/sd_extra_networks/thumb", fetch_file, methods=["GET"]) app.add_api_route("/sd_extra_networks/metadata", get_metadata, methods=["GET"]) app.add_api_route("/sd_extra_networks/info", get_info, methods=["GET"]) @@ -98,16 +94,16 @@ class ExtraNetworksPage: self.refresh_time = None # class additional is to keep old extensions happy self.card = ''' -
+
-
{name}
+
{title}
    - ⏺️ - 🛅 - {card_extra} + ⏺️ + 🛅 + 🛈
    @@ -223,11 +219,11 @@ class ExtraNetworksPage: return [] def create_html(self, item, tabname): - # try: + try: args = { "tabname": json.dumps(tabname), - "name": item["name"].replace('_', ' '), - "title": item["name"], + "name": item["name"], + "title": item["name"].replace('_', ' '), "tags": '|'.join([item.get("tags")] if isinstance(item.get("tags", {}), str) else list(item.get("tags", {}).keys())), "preview": html.escape(item.get("preview", self.link_preview('html/card-no-preview.png'))), "width": shared.opts.extra_networks_card_size, @@ -238,25 +234,23 @@ class ExtraNetworksPage: "description": item.get("description") or "", "local_preview": item.get("local_preview"), "card_click": item.get("onclick", '"' + html.escape(f'return cardClicked({item.get("prompt", None)}, {"true" if self.allow_negative_prompt else "false"})') + '"'), - "card_save_preview": '"' + html.escape('return saveCardPreview(event)') + '"', - "card_save_desc": '"' + html.escape('return saveCardDescription(event)') + '"', - "card_extra": "", } - metadata = item.get("metadata", None) - if metadata is not None and len(metadata) > 0: - card_read_meta = '"' + html.escape(f'return readCardMetadata(event, {json.dumps(self.name)}, {json.dumps(item["name"])})') + '"' - args['card_extra'] += f'📘' - info = item.get("info", None) - if info is not None and len(info) > 0: - card_read_info = '"' + html.escape(f'return readCardInformation(event, {json.dumps(self.name)}, {json.dumps(item["name"])})') + '"' - args['card_extra'] += f'ℹ️' # noqa alias = item.get("alias", None) if alias is not None: args['title'] += f'\nAlias: {alias}' return self.card.format(**args) - # except Exception as e: - # shared.log.error(f'Extra networks item error: page={tabname} item={item["name"]} {e}') - # return "" + except Exception as e: + shared.log.error(f'Extra networks item error: page={tabname} item={item["name"]} {e}') + return "" + + def find_preview_file(self, path): + preview_extensions = ["jpg", "jpeg", "png", "webp", "tiff", "jp2"] + fn = os.path.splitext(path)[0] + files = listdir(os.path.dirname(path)) + for file in [f'{fn}{mid}{ext}' for ext in preview_extensions for mid in ['.thumb.', '.preview.', '.']]: + if file in files: + return file + return 'html/card-no-preview.png' def find_preview(self, path): preview_extensions = ["jpg", "jpeg", "png", "webp", "tiff", "jp2"] @@ -295,42 +289,6 @@ class ExtraNetworksPage: pass return '' - def save_preview(self, index, images, filename): - try: - image = image_from_url_text(images[int(index)]) - except Exception as e: - shared.log.error(f'Extra network save preview: {filename} {e}') - return - is_allowed = False - for page in shared.extra_networks: - if any(path_is_parent(x, filename) for x in page.allowed_directories_for_previews()): - is_allowed = True - break - if not is_allowed: - shared.log.error(f'Extra network save preview: {filename} not allowed') - return - if image.width > 512 or image.height > 512: - image = image.convert('RGB') - image.thumbnail((512, 512), Image.HAMMING) - image.save(filename, quality=50) - fn, _ext = os.path.splitext(filename) - thumb = fn + '.thumb.jpg' - if os.path.exists(thumb): - shared.log.debug(f'Extra network delete thumbnail: {thumb}') - os.remove(thumb) - shared.log.info(f'Extra network save preview: {filename}') - - def save_description(self, filename, desc): - lastDotIndex = filename.rindex('.') - filename = filename[0:lastDotIndex]+".txt" - if desc != "": - try: - with open(filename, 'w', encoding='utf-8') as f: - f.write(desc) - shared.log.info(f'Extra network save description: {filename} {desc}') - except Exception as e: - shared.log.error(f'Extra network save description: {filename} {e}') - def initialize(): shared.extra_networks.clear() @@ -364,31 +322,128 @@ def get_pages(): class ExtraNetworksUi: def __init__(self): - self.pages = None - self.button_save_preview = None - self.preview_target_filename = None - self.button_save_description = None - self.button_read_description = None - self.description_target_filename = None - self.description = None - self.tags = None - self.tabname = None - self.search = None + self.tabname: str = None + self.pages: list(str) = None + self.visible: gr.State = None + self.state: gr.Textbox = None + self.details: gr.Group = None + self.tabs: gr.Tabs = None + self.gallery: gr.Gallery = None + self.description: gr.Textbox = None + self.search: gr.Textbox = None + self.button_details: gr.Button = None + self.details_components: list = [] -def create_ui(container, button, tabname, skip_indexing = False): +def create_ui(container, button_parent, tabname, skip_indexing = False): ui = ExtraNetworksUi() - ui.pages = [] ui.tabname = tabname - with gr.Tabs(elem_id=tabname+"_extra_tabs"): + ui.pages = [] + ui.state = gr.Textbox('{}', elem_id=tabname+"_extra_state", visible=False) + ui.visible = gr.State(value=False) # pylint: disable=abstract-class-instantiated + ui.details = gr.Group(elem_id=tabname+"_extra_details", visible=False) + ui.tabs = gr.Tabs(elem_id=tabname+"_extra_tabs") + ui.button_details = gr.Button('Details', elem_id=tabname+"_extra_details_btn", visible=False) + state = {} + + def get_item(state): + if state is None or state.page is None or state.item is None: + return None, None + page = next(iter([x for x in get_pages() if x.title == state.page]), None) + if page is None: + return None, None + item = next(iter([x for x in page.items if x["name"] == state.item]), None) + if item is None: + return page, None + item = SimpleNamespace(**item) + return page, item + + # main event that is triggered when js updates state text field with json values, used to communicate js -> python + def state_change(state_text): + try: + nonlocal state + state = SimpleNamespace(**json.loads(state_text)) + except Exception as e: + shared.log.error(f'Extra networks state error: {e}') + return + page, item = get_item(state) + shared.log.debug(f'Extra network: op={state.op} page={page.title if page is not None else None} item={item.filename if item is not None else None}') + + if state.op == 'getCardDetails': + pass + + if state.op == 'saveCardDescription': + if item is None or state.description is None or len(state.description) == 0: + return + try: + filename = os.path.splitext(item.filename)[1] + '.txt' + with open(filename, 'w', encoding='utf-8') as f: + f.write(state.description) + shared.log.info(f'Extra network save: file="{filename}" description={state.description}') + except Exception as e: + shared.log.error(f'Extra network save: file="{filename}" {e}') + + if state.op == 'saveCardPreview': + if item is None or item.local_preview is None or len(item.local_preview) == 0: + return + try: + images = list(ui.gallery.temp_files) + if len(images) < 1: + shared.log.error(f'Extra network save: preview="{item.local_preview}" no images') + return + image = Image.open(images[state.index]) + except Exception as e: + shared.log.error(f'Extra network save: preview="{item.local_preview}" {e}') + return + if image.width > 512 or image.height > 512: + image = image.convert('RGB') + image.thumbnail((512, 512), Image.HAMMING) + image.save(item.local_preview, quality=50) + thumb = os.path.splitext(item.local_preview)[0] + '.thumb.jpg' + if os.path.exists(thumb): + shared.log.debug(f'Extra network delete: thumbnail="{thumb}"') + os.remove(thumb) + shared.log.info(f'Extra network save: preview="{item.local_preview}"') + + def toggle_visibility(is_visible): + is_visible = not is_visible + return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary")) + + with ui.details: + details_close = ToolButton(symbols.close, elem_id=tabname+"_extra_details_close") + details_close.click(fn=lambda: gr.update(visible=False), inputs=[], outputs=[ui.details]) + with gr.Row(): + with gr.Column(scale=1): + text = gr.HTML('
    title
    ') + ui.details_components.append(text) + with gr.Column(scale=1): + img = gr.Image(value=None, show_label=False, interactive=False, container=True) + ui.details_components.append(img) + with gr.Row(): + gr.Button('Replace') + gr.Button('Delete') + with gr.Tabs(): + with gr.Tab('Description'): + desc = gr.Textbox('', show_label=False, lines=8) + ui.details_components.append(desc) + with gr.Row(): + gr.Button('Save') + gr.Button('Delete') + with gr.Tab('Info'): + info = gr.Textbox('', show_label=False, lines=8) + ui.details_components.append(info) + with gr.Row(): + gr.Button('Save') + gr.Button('Delete') + with gr.Tab('Metadata'): + meta = gr.JSON({}, show_label=False, lines=8) + ui.details_components.append(meta) + + with ui.tabs: button_refresh = ToolButton(symbols.refresh, elem_id=tabname+"_extra_refresh") button_close = ToolButton(symbols.close, elem_id=tabname+"_extra_close") ui.search = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", elem_classes="textbox", lines=2) ui.description = gr.Textbox('', show_label=False, elem_id=tabname+"_description", placeholder="Save/Replace Extra Network Description...", elem_classes="textbox", lines=2) - ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False) - ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) - ui.description_target_filename = gr.Textbox('Description save filename', elem_id=tabname+"_description_filename", visible=False) - ui.button_save_description = gr.Button('Save description', elem_id=tabname+"_save_description", visible=False) if ui.tabname == 'txt2img': # refresh only once global refresh_time # pylint: disable=global-statement @@ -397,12 +452,29 @@ def create_ui(container, button, tabname, skip_indexing = False): page.create_page(ui.tabname, skip_indexing) with gr.Tab(page.title, id=page.title.lower().replace(" ", "_"), elem_classes="extra-networks-tab"): hmtl = gr.HTML(page.html, elem_id=f'{tabname}{page.name}_extra_page', elem_classes="extra-networks-page") - # hmtl.change(fn=lambda: None, _js=f'() => refreshExtraNetworks("{tabname}")', inputs=[], outputs=[]) ui.pages.append(hmtl) - def toggle_visibility(is_visible): - is_visible = not is_visible - return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary")) + def show_details(text, img, desc, info, meta): + page, item = get_item(state) + if item is not None and os.path.exists(item.filename): + stat = os.stat(item.filename) + text = f''' +

    {item.name}

    + + + + + + + + +
    Type{page.title}
    Alias{getattr(item, 'alias', 'N/A')}
    Tags{getattr(item, 'tags', 'N/A')}
    Filename{item.filename}
    Hash{getattr(item, 'hash', 'N/A')}
    Size{round(stat.st_size/1024/1024, 2)} MB
    Last modified{datetime.fromtimestamp(stat.st_mtime)}
    + ''' + desc = item.description + info = page.info.get(item.name, 'N/A') + meta = page.metadata.get(item.name, 'N/A') + img = page.find_preview_file(item.filename) + return [text, img, desc, info, meta, gr.update(visible=True)] def en_refresh(title): pages = [] @@ -416,49 +488,13 @@ def create_ui(container, button, tabname, skip_indexing = False): ui.search.update(value = ui.search.value) return pages - state_visible = gr.State(value=False) # pylint: disable=abstract-class-instantiated - button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button]) - button_close.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container]) + button_parent.click(fn=toggle_visibility, inputs=[ui.visible], outputs=[ui.visible, container, button_parent]) + button_close.click(fn=toggle_visibility, inputs=[ui.visible], outputs=[ui.visible, container]) button_refresh.click(_js='getENActivePage', fn=en_refresh, inputs=[ui.search], outputs=ui.pages) + ui.state.change(state_change, inputs=[ui.state], outputs=[]) + ui.button_details.click(show_details, _js="getCardDetails", inputs=ui.details_components, outputs=ui.details_components + [ui.details]) return ui -def path_is_parent(parent_path, child_path): - parent_path = os.path.abspath(parent_path) - child_path = os.path.abspath(child_path) - return child_path.startswith(parent_path) - - def setup_ui(ui, gallery): - - def save_preview(pagename, index, images, filename): - pages = [] - for page in get_pages(): - if pagename is None or pagename == '' or pagename == page.title or len(page.html) == 0: - page.save_preview(index, images, filename) - page.create_page(ui.tabname) - pages.append(page.html) - return pages - - ui.button_save_preview.click( - fn=save_preview, - _js="function(t, i, y, z) {return [getENActivePage(), selected_gallery_index(), y, z]}", - inputs=[ui.search, ui.preview_target_filename, gallery, ui.preview_target_filename], - outputs=ui.pages - ) - - def save_description(pagename, filename, desc): - pages = [] - for page in get_pages(): - if pagename is None or pagename == '' or pagename == page.title or len(page.html) == 0: - page.save_description(filename, desc) - page.create_page(ui.tabname) - pages.append(page.html) - return pages - - ui.button_save_description.click( - fn=save_description, - _js="function(t, x, y) { return [getENActivePage(), x, y] }", - inputs=[ui.search, ui.description_target_filename, ui.description], - outputs=ui.pages - ) + ui.gallery = gallery diff --git a/webui.py b/webui.py index 5ffd0673c..914353bea 100644 --- a/webui.py +++ b/webui.py @@ -281,7 +281,7 @@ def start_ui(): create_api(app) timer.startup.record("api") - ui_extra_networks.add_pages_to_demo(app) + ui_extra_networks.init_api(app) modules.script_callbacks.app_started_callback(shared.demo, app) timer.startup.record("app-started")