diff --git a/CHANGELOG.md b/CHANGELOG.md index a20159db8..05767d917 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2026-05-02 +## Update for 2026-05-04 - **Features** - **Multi-image** workflows! @@ -9,6 +9,7 @@ - option *inputs -> skip processing* to force images to passed to model as-is without any pre-processing examples of models that support multi-inputs: *qwen-image-edit, flux.2, google-gemini* - **Anima** support for *img2img* and *inpaint* workflows + - **LTX** support for *audio* generation - custom **VAE** loader for all pipelines *note*: vae still needs to be compatible with the model - add prompt enhance info to image metadata @@ -19,6 +20,7 @@ - **Kanvas** re-order stages by clicking on active stage order of stages detemines order of images passed to model - **Kanvas** *magic-wand* tool now works on mask layer and auto-creates mask based on perceptual tolerance + - **Gallery** add thumbnail size slider - **Control** - remove buttons: *input/control/process* - move params *control input type* to control menu section diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui index 10f1a67a1..289f4dcd8 160000 --- a/extensions-builtin/sdnext-modernui +++ b/extensions-builtin/sdnext-modernui @@ -1 +1 @@ -Subproject commit 10f1a67a1f42c0207a71b4dfb4eb1c306c9d1b74 +Subproject commit 289f4dcd80c92eedd376a5acb0c9afcfc3a0be32 diff --git a/javascript/gallery.js b/javascript/gallery.js index 377577892..c901442f7 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -1,6 +1,8 @@ /* eslint-disable max-classes-per-file */ let ws; let url; +let currentSize = 0; +let currentSort = 'none'; let currentImage = null; let currentGalleryFolder = null; let pruneImagesTimer; @@ -19,6 +21,7 @@ const el = { status: undefined, btnSend: undefined, clearCacheFolder: undefined, + size: undefined, }; const SUPPORTED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'tiff', 'jp2', 'jxl', 'gif', 'mp4', 'mkv', 'avi', 'mjpeg', 'mpg', 'avr']; @@ -26,12 +29,12 @@ const SUPPORTED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'tiff', 'jp2', 'jxl' const gallerySorter = { nameA: { name: 'Name Ascending', func: (a, b) => a.name.localeCompare(b.name) }, nameD: { name: 'Name Descending', func: (b, a) => a.name.localeCompare(b.name) }, - sizeA: { name: 'Size Ascending', func: (a, b) => a.size - b.size }, - sizeD: { name: 'Size Descending', func: (b, a) => a.size - b.size }, - resA: { name: 'Resolution Ascending', func: (a, b) => a.width * a.height - b.width * b.height }, - resD: { name: 'Resolution Descending', func: (b, a) => a.width * a.height - b.width * b.height }, - modA: { name: 'Modified Ascending', func: (a, b) => a.mtime - b.mtime }, - modD: { name: 'Modified Descending', func: (b, a) => a.mtime - b.mtime }, + sizeD: { name: 'Size Ascending', func: (a, b) => a.size - b.size }, + sizeA: { name: 'Size Descending', func: (b, a) => a.size - b.size }, + resD: { name: 'Resolution Ascending', func: (a, b) => a.width * a.height - b.width * b.height }, + resA: { name: 'Resolution Descending', func: (b, a) => a.width * a.height - b.width * b.height }, + modD: { name: 'Modified Ascending', func: (a, b) => a.mtime - b.mtime }, + modA: { name: 'Modified Descending', func: (b, a) => a.mtime - b.mtime }, none: { name: 'None', func: undefined }, }; @@ -179,12 +182,13 @@ function updateGalleryStyles() { } `); } + const size = el.size ? el.size.value : opts.extra_networks_card_size; fileStylesheet.replaceSync(` .gallery-file { object-fit: contain; cursor: pointer; - height: ${opts.extra_networks_card_size}px; - width: ${opts.browser_fixed_width ? `${opts.extra_networks_card_size}px` : 'unset'}; + height: ${size}px; + width: ${opts.browser_fixed_width ? `${size}px` : 'unset'}; } .gallery-file:hover { filter: grayscale(100%); @@ -558,8 +562,10 @@ class GalleryFile extends HTMLElement { } async function createThumb(img) { - const height = opts.extra_networks_card_size; - const width = opts.browser_fixed_width ? opts.extra_networks_card_size : 0; + const sizeEl = document.getElementById('gallery-thumb-size'); + currentSize = sizeEl ? parseInt(sizeEl.value, 10) : opts.extra_networks_card_size; + const height = currentSize; + const width = opts.browser_fixed_width ? currentSize : 0; const canvas = document.createElement('canvas'); const scaleY = height / img.height; const scaleX = width > 0 ? width / img.width : scaleY; @@ -872,8 +878,13 @@ const findDuplicates = (arr, key) => { }; async function gallerySort(key) { - if (!Object.hasOwn(gallerySorter, key)) { - error(`Gallery: "${key}" is not a valid gallery sorting key`); + // if currentSort does not start with key, default to key+A + // else if currentSort ends with A change to D and vice versa for toggling sort order + if (currentSort.startsWith(key)) currentSort = currentSort.endsWith('A') ? `${key}D` : `${key}A`; + else currentSort = `${key}A`; + + if (!Object.hasOwn(gallerySorter, currentSort)) { + error(`Gallery: "${currentSort}" is not a valid gallery sorting key`); return; } const t0 = performance.now(); @@ -901,7 +912,7 @@ async function gallerySort(key) { folderGroups.get(dir).push(file); } - sortMode = gallerySorter[key]; + sortMode = gallerySorter[currentSort]; // Sort root files rootFiles.sort(sortMode.func); @@ -1329,22 +1340,26 @@ async function initGallery() { // triggered on gradio change to monitor when ui el.files = gradioApp().getElementById('tab-gallery-files'); el.status = gradioApp().getElementById('tab-gallery-status'); el.search = gradioApp().querySelector('#tab-gallery-search textarea'); + el.size = document.getElementById('tab-gallery-thumb-size'); if (!el.folders || !el.files || !el.status || !el.search) { error('initGallery', 'Missing gallery elements'); return; } + if (el.size) { + el.size.value = opts.extra_networks_card_size; + el.size.addEventListener('input', updateGalleryStyles); + } blockQueueUntilReady(); // Run first updateGalleryStyles(); injectGalleryStatusCSS(); setOverlayAnimation(); galleryClearInit(); + const progress = gradioApp().getElementById('tab-gallery-progress'); - if (progress) { - galleryProgressBar.attachTo(progress); - } else { - log('initGallery', 'Failed to attach loading progress bar'); - } + if (progress) galleryProgressBar.attachTo(progress); + else log('initGallery', 'Failed to attach loading progress bar'); + el.search.addEventListener('input', gallerySearch); el.btnSend = gradioApp().getElementById('tab-gallery-send-image'); document.getElementById('tab-gallery-files').style.height = opts.logmonitor_show ? '75vh' : '85vh'; diff --git a/javascript/startup.js b/javascript/startup.js index 1aa1e73c5..1a9751523 100644 --- a/javascript/startup.js +++ b/javascript/startup.js @@ -65,14 +65,14 @@ async function initStartup() { } executeCallbacks(uiReadyCallbacks); - startupPromises.push(initGallery()); - startupPromises.push(setRefreshInterval()); - startupPromises.push(setupExtraNetworks()); // optinally wait for modern ui if (window.waitForUiReady) await waitForUiReady(); // post startup tasks that may take longer but are not critical + startupPromises.push(initGallery()); + startupPromises.push(setRefreshInterval()); + startupPromises.push(setupExtraNetworks()); startupPromises.push(initAutocomplete()); startupPromises.push(monitorConnection()); startupPromises.push(showNetworks()); diff --git a/modules/ui_gallery.py b/modules/ui_gallery.py index 9791598b3..c953cb05a 100644 --- a/modules/ui_gallery.py +++ b/modules/ui_gallery.py @@ -59,6 +59,7 @@ def create_ui(): with gr.Blocks() as tab: with gr.Row(elem_id='tab-gallery-sort-buttons'): sort_buttons = [] + """ sort_buttons.append(sort_nameA := ToolButton(value=ui_symbols.sort_alpha_asc, elem_classes=['gallery-sort'])) sort_buttons.append(sort_nameD := ToolButton(value=ui_symbols.sort_alpha_dsc, elem_classes=['gallery-sort'])) sort_buttons.append(sort_sizeA := ToolButton(value=ui_symbols.sort_size_asc, elem_classes=['gallery-sort'])) @@ -67,6 +68,11 @@ def create_ui(): sort_buttons.append(sort_resD := ToolButton(value=ui_symbols.sort_num_dsc, elem_classes=['gallery-sort'])) sort_buttons.append(sort_modA := ToolButton(value=ui_symbols.sort_time_asc, elem_classes=['gallery-sort'])) sort_buttons.append(sort_modD := ToolButton(value=ui_symbols.sort_time_dsc, elem_classes=['gallery-sort'])) + """ + sort_buttons.append(sort_name := ToolButton(value=ui_symbols.sort_alpha, elem_classes=['gallery-sort'])) + sort_buttons.append(sort_size := ToolButton(value=ui_symbols.sort_size, elem_classes=['gallery-sort'])) + sort_buttons.append(sort_res := ToolButton(value=ui_symbols.sort_num, elem_classes=['gallery-sort'])) + sort_buttons.append(sort_time := ToolButton(value=ui_symbols.sort_time, elem_classes=['gallery-sort'])) gr.Textbox(show_label=False, placeholder='Search', elem_id='tab-gallery-search') gr.HTML('', elem_id='tab-gallery-status') gr.HTML('', elem_id='tab-gallery-progress') @@ -81,6 +87,7 @@ def create_ui(): gallery_images, gen_info, html_info, _html_info_formatted, html_log = ui_common.create_output_panel("gallery") btn_gallery_image.click(fn=read_media, _js='gallerySendImage', inputs=[html_info], outputs=[gallery_images, gallery_video, html_info, gen_info, html_log]) + """ sort_nameA.click(fn=None, _js='() => gallerySort("nameA")') sort_nameD.click(fn=None, _js='() => gallerySort("nameD")') sort_sizeA.click(fn=None, _js='() => gallerySort("sizeA")') @@ -89,5 +96,10 @@ def create_ui(): sort_resD.click(fn=None, _js='() => gallerySort("resD")') sort_modA.click(fn=None, _js='() => gallerySort("modA")') sort_modD.click(fn=None, _js='() => gallerySort("modD")') + """ + sort_name.click(fn=None, _js='() => gallerySort("name")') + sort_size.click(fn=None, _js='() => gallerySort("size")') + sort_res.click(fn=None, _js='() => gallerySort("res")') + sort_time.click(fn=None, _js='() => gallerySort("mod")') return [(tab, 'Gallery', 'tab-gallery')] diff --git a/modules/ui_symbols.py b/modules/ui_symbols.py index 6819cb5f6..1a82cfc31 100644 --- a/modules/ui_symbols.py +++ b/modules/ui_symbols.py @@ -38,12 +38,16 @@ bullet = '⃝' vision = '\uf06e' # Font Awesome eye icon (more minimalistic) reasoning = '\uf0eb' # Font Awesome lightbulb icon (represents thinking/reasoning) cloud = '☁' +sort_alpha = '\uf15d' sort_alpha_asc = '\uf15d' sort_alpha_dsc = '\uf15e' +sort_size = '\uf160' sort_size_asc = '\uf160' sort_size_dsc = '\uf161' +sort_num = '\uf162' sort_num_asc = '\uf162' sort_num_dsc = '\uf163' +sort_time = '\uf0dc' sort_time_asc = '\uf0de' sort_time_dsc = '\uf0dd' style_apply = '↶'