diff --git a/CHANGELOG.md b/CHANGELOG.md index d86b90f49..b040ecb60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ # Change Log for SD.Next - fix startup issue with incorrect config -- image viewer: fixes for non-chromium browsers +- image viewer: fixes for non-chromium browsers and mobile users and add option to download image - extra network: fix corrupt display on refesh when new extra network type found - diffusers: option to move base and/or refiner model to cpu to free up vram - diffusers: model downloader options to specify model variant/revision/mirror +- diffusers: updated hints for settings ## Update for 07/08/2023 diff --git a/javascript/imageViewer.js b/javascript/imageViewer.js index e9f9631c7..e0cceb494 100644 --- a/javascript/imageViewer.js +++ b/javascript/imageViewer.js @@ -2,7 +2,8 @@ let previewTimestamp = Date.now(); let previewDrag = false; -function closeModal() { +function closeModal(force = false) { + if (force) gradioApp().getElementById('lightboxModal').style.display = 'none'; if (previewDrag) return; if ((Date.now() - previewTimestamp) < 250) return gradioApp().getElementById('lightboxModal').style.display = 'none'; @@ -16,18 +17,10 @@ function showModal(event) { if (modalImage.style.display === 'none') lb.style.setProperty('background-image', `url(${source.src})`); lb.style.display = 'flex'; lb.focus(); - const tabTxt2Img = gradioApp().getElementById('tab_txt2img'); - const tabImg2Img = gradioApp().getElementById('tab_img2img'); - if (tabTxt2Img.style.display != 'none' || tabImg2Img.style.display != 'none') gradioApp().getElementById('modal_save').style.display = 'inline'; - else gradioApp().getElementById('modal_save').style.display = 'none'; previewTimestamp = Date.now() event.stopPropagation(); } -function negmod(n, m) { - return ((n % m) + m) % m; -} - function updateOnBackgroundChange() { const modalImage = gradioApp().getElementById('modalImage'); if (modalImage && modalImage.offsetParent) { @@ -47,6 +40,7 @@ function modalImageSwitch(offset) { galleryButtons.forEach((v, i) => { if (v == currentButton) result = i; }); + const negmod = (n, m) => ((n % m) + m) % m; if (result != -1) { nextButton = galleryButtons[negmod((result + offset), galleryButtons.length)]; nextButton.click(); @@ -60,13 +54,22 @@ function modalImageSwitch(offset) { } function saveImage() { - const tabTxt2Img = gradioApp().getElementById('tab_txt2img'); - const tabImg2Img = gradioApp().getElementById('tab_img2img'); - const saveTxt2Img = 'save_txt2img'; - const saveImg2Img = 'save_img2img'; - if (tabTxt2Img.style.display != 'none') gradioApp().getElementById(saveTxt2Img).click(); - else if (tabImg2Img.style.display != 'none') gradioApp().getElementById(saveImg2Img).click(); - else console.error('missing implementation for saving modal of this type'); + if (gradioApp().getElementById('tab_txt2img').style.display != 'none') gradioApp().getElementById('save_txt2img').click(); + else if (gradioApp().getElementById('tab_img2img').style.display != 'none') gradioApp().getElementById('save_img2img').click(); + else if (gradioApp().getElementById('tab_process').style.display != 'none') gradioApp().getElementById('save_extras').click(); +} + +function modalDownloadImage() { + const link = document.createElement("a"); + link.style.display = "none"; + link.href = gradioApp().getElementById('modalImage').src; + link.download = 'image'; + document.body.appendChild(link); + link.click(); + setTimeout(() => { + URL.revokeObjectURL(link.href); + link.parentNode.removeChild(link); + }, 0); } function modalSaveImage(event) { @@ -96,7 +99,7 @@ function modalKeyHandler(event) { modalNextImage(event); break; case 'Escape': - closeModal(); + closeModal(true); break; } } @@ -127,7 +130,7 @@ function modalZoomToggle(event) { event.stopPropagation(); } -function modalTileImageToggle(event) { +function modalTileToggle(event) { const modalImage = gradioApp().getElementById('modalImage'); const modal = gradioApp().getElementById('lightboxModal'); const isTiling = modalImage.style.display === 'none'; @@ -152,48 +155,57 @@ onAfterUiUpdate(() => { }); document.addEventListener('DOMContentLoaded', () => { + // main elements const modal = document.createElement('div'); modal.id = 'lightboxModal'; - modal.tabIndex = 0; modal.addEventListener('keydown', modalKeyHandler, true); const modalPreviewZone = document.createElement('div'); modalPreviewZone.className = 'lightboxModalPreviewZone' - modalPreviewZone.tabIndex = 0; - - const modalControls = document.createElement('div'); - modalControls.className = 'modalControls gradio-container'; - modal.append(modalControls); - - const modalZoom = document.createElement('span'); - modalZoom.className = 'modalZoom cursor'; - modalZoom.innerHTML = '⤡'; - modalZoom.addEventListener('click', modalZoomToggle, true); - modalZoom.title = 'Toggle zoomed view'; - modalControls.appendChild(modalZoom); - - const modalTileImage = document.createElement('span'); - modalTileImage.className = 'modalTileImage cursor'; - modalTileImage.innerHTML = '⊞'; - modalTileImage.addEventListener('click', modalTileImageToggle, true); - modalTileImage.title = 'Preview tiling'; - modalControls.appendChild(modalTileImage); - - const modalSave = document.createElement('span'); - modalSave.className = 'modalSave cursor'; - modalSave.id = 'modal_save'; - modalSave.innerHTML = '🖫'; - modalSave.addEventListener('click', modalSaveImage, true); - modalSave.title = 'Save Image(s)'; - modalControls.appendChild(modalSave); const modalImage = document.createElement('img'); modalImage.id = 'modalImage'; - modalImage.tabIndex = 0; modalImage.addEventListener('keydown', modalKeyHandler, true); modalPreviewZone.appendChild(modalImage); modalImage.onload = () => panzoom(modalImage, { zoomSpeed: 0.05, minZoom: 0.25, maxZoom: 4.0 }); + // toolbar + const modalZoom = document.createElement('span'); + modalZoom.id = 'modal_zoom'; + modalZoom.className = 'cursor'; + modalZoom.innerHTML = '🔍'; + modalZoom.title = 'Toggle zoomed view'; + modalZoom.addEventListener('click', modalZoomToggle, true); + + const modalTile = document.createElement('span'); + modalTile.id = 'modal_tile'; + modalTile.className = 'cursor'; + modalTile.innerHTML = '🖽'; + modalTile.title = 'Preview tiling'; + modalTile.addEventListener('click', modalTileToggle, true); + + const modalSave = document.createElement('span'); + modalSave.id = 'modal_save'; + modalSave.className = 'cursor'; + modalSave.innerHTML = '💾'; + modalSave.title = 'Save Image'; + modalSave.addEventListener('click', modalSaveImage, true); + + const modalDownload = document.createElement('span'); + modalDownload.id = 'modal_download'; + modalDownload.className = 'cursor'; + modalDownload.innerHTML = '📷'; + modalDownload.title = 'Download Image'; + modalDownload.addEventListener('click', modalDownloadImage, true); + + const modalClose = document.createElement('span'); + modalClose.id = 'modal_close'; + modalClose.className = 'cursor'; + modalClose.innerHTML = '🗙'; + modalClose.title = 'Close'; + modalClose.addEventListener('click', closeModal, true); + + // handlers modalPreviewZone.addEventListener('mousedown', () => previewDrag = false); modalPreviewZone.addEventListener('touchstart', () => previewDrag = false); modalPreviewZone.addEventListener('mousemove', () => previewDrag = true); @@ -205,19 +217,28 @@ document.addEventListener('DOMContentLoaded', () => { const modalPrev = document.createElement('a'); modalPrev.className = 'modalPrev'; modalPrev.innerHTML = '❮'; - modalPrev.tabIndex = 0; modalPrev.addEventListener('click', modalPrevImage, true); modalPrev.addEventListener('keydown', modalKeyHandler, true); - modal.appendChild(modalPrev); - modal.appendChild(modalPreviewZone); const modalNext = document.createElement('a'); modalNext.className = 'modalNext'; modalNext.innerHTML = '❯'; - modalNext.tabIndex = 0; modalNext.addEventListener('click', modalNextImage, true); modalNext.addEventListener('keydown', modalKeyHandler, true); + + const modalControls = document.createElement('div'); + modalControls.className = 'modalControls gradio-container'; + + // build interface + modal.appendChild(modalPrev); + modal.appendChild(modalPreviewZone); modal.appendChild(modalNext); + modal.append(modalControls); + modalControls.appendChild(modalZoom); + modalControls.appendChild(modalTile); + modalControls.appendChild(modalSave); + modalControls.appendChild(modalDownload); + modalControls.appendChild(modalClose); try { gradioApp().appendChild(modal); } catch (e) { gradioApp().body.appendChild(modal); } }); diff --git a/javascript/style.css b/javascript/style.css index 91ebd8362..a9d5d2c2b 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -394,50 +394,13 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } -.modalControls { - display: flex; - gap: 1em; - padding: 1em; - background-color: rgba(0,0,0,0.2); - position: absolute; - width: fit-content; - z-index: 1; -} - -.modalClose { - margin-left: auto; -} - -.modalControls span{ - color: white; - font-size: 35px; - font-weight: bold; - cursor: pointer; - width: 1em; -} - -.modalControls span:hover, .modalControls span:focus{ - color: #999; - text-decoration: none; -} -.lightboxModalPreviewZone { - display: flex; - width: 100%; - height: 100%; -} -.lightboxModalPreviewZone > img { - display: block; - margin: auto; - width: auto; -} - -.lightboxModalPreviewZone > img.modalImageFullscreen{ - object-fit: contain; - height: 100%; - width: 100%; - min-height: 0; - background: transparent; -} +.modalControls { display: flex; justify-content: space-evenly; background-color: transparent; position: absolute; width: -webkit-fill-available; z-index: 1; } +.modalControls:hover { background-color: #50505050; } +.modalControls span { color: white; font-size: 2em; font-weight: bold; cursor: pointer; filter: grayscale(100%); } +.modalControls span:hover, .modalControls span:focus { color: var(--highlight-color); filter: none; } +.lightboxModalPreviewZone { display: flex; width: 100%; height: 100%; } +.lightboxModalPreviewZone > img { display: block; margin: auto; width: auto; } +.lightboxModalPreviewZone > img.modalImageFullscreen{ object-fit: contain; height: 100%; width: 100%; min-height: 0; background: transparent; } table.settings-value-table{ background: white;