From 5f6edafc8bfea7d7464d16f9f0e8f5054c71ee3a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 30 Aug 2025 14:34:41 -0400 Subject: [PATCH] modernui mobile optimizations Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 2 + installer.py | 20 +++-- javascript/base.css | 6 +- javascript/black-teal-reimagined.css | 4 +- javascript/sdnext.css | 2 +- javascript/setHints.js | 115 ++++++++------------------- javascript/startup.js | 24 +++--- requirements.txt | 1 - 8 files changed, 66 insertions(+), 108 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b270e54..8f520bbd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - **UI** - default to **ModernUI** standard ui is still available via *settings -> user interface -> theme type* + - mobile-friendly! + - make hints touch-friendly: hold touch to display hint - improved image scaling in img2img and control interfaces - add base model type to networks display, thanks @Artheriax - additional hints to ui, thanks @Artheriax diff --git a/installer.py b/installer.py index 17fda8161..fb52ffc64 100644 --- a/installer.py +++ b/installer.py @@ -622,18 +622,22 @@ def check_transformers(): t_start = time.time() if args.skip_all or args.skip_git or args.experimental: return - pkg = pkg_resources.working_set.by_key.get('transformers', None) + pkg_transofmers = pkg_resources.working_set.by_key.get('transformers', None) + pkg_tokenizers = pkg_resources.working_set.by_key.get('tokenizers', None) if args.use_directml: - target = '4.52.4' + target_transformers = '4.52.4' + target_tokenizers = '0.21.4' else: - target = '4.56.0' - if (pkg is None) or ((pkg.version != target) and (not args.experimental)): - if pkg is None: - log.info(f'Transformers install: version={target}') + target_transformers = '4.56.0' + target_tokenizers = '0.22.0' + if (pkg_transofmers is None) or ((pkg_transofmers.version != target_transformers) or (pkg_tokenizers is None) or ((pkg_tokenizers.version != target_tokenizers) and (not args.experimental))): + if pkg_transofmers is None: + log.info(f'Transformers install: version={target_transformers}') else: - log.info(f'Transformers update: current={pkg.version} target={target}') + log.info(f'Transformers update: current={pkg_transofmers.version} target={target_transformers}') pip('uninstall --yes transformers', ignore=True, quiet=True, uv=False) - pip(f'install --upgrade transformers=={target}', ignore=False, quiet=True, uv=False) + pip(f'install --upgrade tokenizers=={target_tokenizers}', ignore=False, quiet=True, uv=False) + pip(f'install --upgrade transformers=={target_transformers}', ignore=False, quiet=True, uv=False) ts('transformers', t_start) diff --git a/javascript/base.css b/javascript/base.css index d98181cf7..cc2e22061 100644 --- a/javascript/base.css +++ b/javascript/base.css @@ -128,8 +128,8 @@ div:has(>#tab-browser-folders) { flex-grow: 0 !important; background-color: var( /* loader */ .splash { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 1000; display: block; text-align: center; } -.motd { margin-top: 2em; color: var(--body-text-color-subdued); font-family: monospace; font-variant: all-petite-caps; } -.splash-img { margin: 10% auto 0 auto; width: 512px; background-repeat: no-repeat; height: 512px; animation: color 10s infinite alternate; max-width: 80vw; background-size: contain; } +.motd { margin-top: 2em; color: var(--body-text-color-subdued); font-family: monospace; font-variant: all-petite-caps; font-size: 1.2em; } +.splash-img { margin: 10% auto 0 auto; width: 512px; background-repeat: no-repeat; height: 512px; animation: hue 5s infinite alternate; max-width: 80vw; background-size: contain; } .loading { color: white; position: absolute; top: 20%; left: 50%; transform: translateX(-50%); } .loader { width: 300px; height: 300px; border: var(--spacing-md) solid transparent; border-radius: 50%; border-top: var(--spacing-md) solid var(--primary-600); animation: spin 4s linear infinite; position: relative; } .loader::before, .loader::after { content: ""; position: absolute; top: 6px; bottom: 6px; left: 6px; right: 6px; border-radius: 50%; border: var(--spacing-md) solid transparent; } @@ -137,4 +137,4 @@ div:has(>#tab-browser-folders) { flex-grow: 0 !important; background-color: var( .loader::after { border-top-color: var(--primary-300); animation: spin 1.5s linear infinite; } @keyframes move { from { background-position-x: 0, -40px; } to { background-position-x: 0, 40px; } } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } -@keyframes color { from { filter: hue-rotate(0deg) } to { filter: hue-rotate(360deg) } } +@keyframes hue { from { filter: hue-rotate(0deg) } to { filter: hue-rotate(360deg) } } diff --git a/javascript/black-teal-reimagined.css b/javascript/black-teal-reimagined.css index 1e7d4dc0b..cd2d7d524 100644 --- a/javascript/black-teal-reimagined.css +++ b/javascript/black-teal-reimagined.css @@ -1007,11 +1007,11 @@ svg.feather.feather-image, } .splash-img { - margin: 0; + margin: 10% auto 0 auto; width: 512px; height: 512px; background-repeat: no-repeat; - animation: color 8s infinite alternate, move 3s infinite alternate; + animation: hue 5s infinite alternate; } .loading { diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 6f149757a..7c4b6628d 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -1946,7 +1946,7 @@ div:has(>#tab-gallery-folders) { } .splash-img { - margin: 0; + margin: 10% auto 0 auto; width: 512px; height: 512px; background-repeat: no-repeat; diff --git a/javascript/setHints.js b/javascript/setHints.js index 0db1faff7..4dcd2a39f 100644 --- a/javascript/setHints.js +++ b/javascript/setHints.js @@ -14,6 +14,7 @@ const localeData = { observer: null, // MutationObserver for DOM changes }; let localeTimeout = null; +const isTouchDevice = 'ontouchstart' in window; async function cycleLocale() { clearTimeout(localeTimeout); @@ -64,66 +65,48 @@ async function tooltipCreate() { if (window.opts.tooltips === 'Browser default') localeData.type = 1; if (window.opts.tooltips === 'UI tooltips') localeData.type = 2; - // Setup event delegation for tooltips instead of individual listeners - if (localeData.type === 2) { - gradioApp().addEventListener('mouseover', tooltipShowDelegated); // eslint-disable-line no-use-before-define - gradioApp().addEventListener('mouseout', tooltipHideDelegated); // eslint-disable-line no-use-before-define - } - - // Initialize DOM observer for immediate hint application - if (!localeData.observer) { - initializeDOMObserver(); // eslint-disable-line no-use-before-define + if (localeData.type === 2) { // setup event delegation for tooltips instead of individual listeners + if (isTouchDevice) { + gradioApp().addEventListener('touchstart', tooltipShowDelegated); // eslint-disable-line no-use-before-define + gradioApp().addEventListener('touchend', tooltipHideDelegated); // eslint-disable-line no-use-before-define + } + gradioApp().addEventListener('pointerover', tooltipShowDelegated); // eslint-disable-line no-use-before-define + gradioApp().addEventListener('pointerout', tooltipHideDelegated); // eslint-disable-line no-use-before-define } + if (!localeData.observer) initializeDOMObserver(); // eslint-disable-line no-use-before-define } async function expandTooltip(element, longHint) { if (localeData.currentElement === element && localeData.hint.classList.contains('tooltip-show')) { - // Hide the progress ring const ring = localeData.hint.querySelector('.tooltip-progress-ring'); - if (ring) { - ring.style.opacity = '0'; - } - - // Expand the container + if (ring) ring.style.opacity = '0'; localeData.hint.classList.add('tooltip-expanded'); - - // After container starts expanding, reveal the long content setTimeout(() => { const longContent = localeData.hint.querySelector('.long-content'); - if (longContent) { - longContent.classList.add('show'); - } + if (longContent) longContent.classList.add('show'); }, 100); } } -async function tooltipShowDelegated(e) { - // Use event delegation to handle dynamically created elements - if (e.target.dataset && e.target.dataset.hint) { - tooltipShow(e); // eslint-disable-line no-use-before-define - } +async function tooltipShowDelegated(e) { // use event delegation to handle dynamically created elements + if (e.target.dataset && e.target.dataset.hint) tooltipShow(e); // eslint-disable-line no-use-before-define } async function tooltipHideDelegated(e) { - if (e.target.dataset && e.target.dataset.hint) { - tooltipHide(e); // eslint-disable-line no-use-before-define - } + if (e.target.dataset && e.target.dataset.hint) tooltipHide(e); // eslint-disable-line no-use-before-define } async function tooltipShow(e) { - // Clear any existing expansion timeout - if (localeData.expandTimeout) { + if (localeData.expandTimeout) { // clear any existing expansion timeout clearTimeout(localeData.expandTimeout); localeData.expandTimeout = null; } - // Remove expanded class and reset current element - localeData.hint.classList.remove('tooltip-expanded'); + localeData.hint.classList.remove('tooltip-expanded'); // remove expanded class and reset current element localeData.currentElement = e.target; if (e.target.dataset.hint) { - // Create progress ring SVG - const progressRing = ` + const progressRing = ` // create progress ring SVG
@@ -131,8 +114,7 @@ async function tooltipShow(e) {
`; - - // Set up the complete content structure from the start + // set up the complete content structure from the start let content = `
${e.target.textContent} @@ -141,21 +123,12 @@ async function tooltipShow(e) {
${e.target.dataset.hint} `; - - // Add long content if available, but keep it hidden - if (e.target.dataset.longHint) { - content += `
${e.target.dataset.longHint}
`; - } - - // Add reload notice if needed - if (e.target.dataset.reload) { + if (e.target.dataset.longHint) content += `
${e.target.dataset.longHint}
`; // add long content if available, but keep it hidden + if (e.target.dataset.reload) { // add reload notice if needed const reloadType = e.target.dataset.reload; let reloadText = ''; - if (reloadType === 'model') { - reloadText = 'Requires model reload'; - } else if (reloadType === 'server') { - reloadText = 'Requires server restart'; - } + if (reloadType === 'model') reloadText = 'Requires model reload'; + else if (reloadType === 'server') reloadText = 'Requires server restart'; if (reloadText) { content += `
@@ -169,40 +142,28 @@ async function tooltipShow(e) { localeData.hint.innerHTML = content; localeData.hint.classList.add('tooltip-show'); - if (e.clientX > window.innerWidth / 2) { - localeData.hint.classList.add('tooltip-left'); - } else { - localeData.hint.classList.remove('tooltip-left'); - } + if (e.clientX > window.innerWidth / 2) localeData.hint.classList.add('tooltip-left'); + else localeData.hint.classList.remove('tooltip-left'); - // Set up expansion timer if long hint is available - if (e.target.dataset.longHint) { - // Start progress ring animation - const ring = localeData.hint.querySelector('.tooltip-progress-ring'); + if (e.target.dataset.longHint) { // set up expansion timer if long hint is available + const ring = localeData.hint.querySelector('.tooltip-progress-ring'); // start progress ring animation const ringProgress = localeData.hint.querySelector('.ring-progress'); - if (ring && ringProgress) { - // Show the ring and start animation setTimeout(() => { ring.classList.add('active'); ringProgress.classList.add('animate'); }, 100); } - - localeData.expandTimeout = setTimeout(() => { - expandTooltip(e.target, e.target.dataset.longHint); - }, 3000); + localeData.expandTimeout = setTimeout(() => expandTooltip(e.target, e.target.dataset.longHint), 3000); } } } async function tooltipHide(e) { - // Clear expansion timeout when hiding if (localeData.expandTimeout) { clearTimeout(localeData.expandTimeout); localeData.expandTimeout = null; } - localeData.hint.classList.remove('tooltip-show', 'tooltip-expanded'); localeData.currentElement = null; } @@ -368,6 +329,7 @@ async function setHints(analyze = false) { localeData.initial = false; const t1 = performance.now(); // localeData.btn.style.backgroundColor = localeData.locale !== 'en' ? 'var(--primary-500)' : ''; + log('touchDevice', isTouchDevice); log('setHints', { type: localeData.type, locale: localeData.locale, elements: elements.length, localized, hints, data: localeData.data.length, override: overrideData.length, time: Math.round(t1 - t0) }); // sortUIElements(); if (analyze) { @@ -388,31 +350,22 @@ async function applyHintToElement(el) { if (!localeData.data || localeData.data.length === 0) return; if (!el.textContent) return; - // Check if element matches our selector criteria + // check if element matches our selector criteria const isValidElement = el.tagName === 'BUTTON' || el.tagName === 'H2' || (el.tagName === 'SPAN' && (el.parentElement?.tagName === 'LABEL' || el.parentElement?.classList.contains('label-wrap'))); - if (!isValidElement) return; - // Find matching hint data - let found; - if (el.dataset.original) { - found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.dataset.original.toLowerCase().trim()); - } else { - found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.textContent.toLowerCase().trim()); - } + let found; // find matching hint data + if (el.dataset.original) found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.dataset.original.toLowerCase().trim()); + else found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.textContent.toLowerCase().trim()); - // Apply localization if found - if (found?.localized?.length > 0) { + if (found?.localized?.length > 0) { // apply localization if found if (!el.dataset.original) el.dataset.original = el.textContent; replaceTextContent(el, found.localized); } - // Apply hint if found - if (found?.hint?.length > 0) { - setHint(el, found); - } + if (found?.hint?.length > 0) setHint(el, found); // apply hint if found } // Initialize MutationObserver for immediate hint application diff --git a/javascript/startup.js b/javascript/startup.js index 842f925ba..86d7078f0 100644 --- a/javascript/startup.js +++ b/javascript/startup.js @@ -8,18 +8,18 @@ async function initStartup() { if (window.setupLogger) await setupLogger(); // all items here are non-blocking async calls - initModels(); - getUIDefaults(); - initPromptChecker(); - initContextMenu(); - initDragDrop(); - initAccordions(); - initSettings(); - initImageViewer(); - initGallery(); - initiGenerationParams(); - initChangelog(); - setupControlUI(); + await initModels(); + await getUIDefaults(); + await initPromptChecker(); + await initContextMenu(); + await initDragDrop(); + await initAccordions(); + await initSettings(); + await initImageViewer(); + await initGallery(); + await initiGenerationParams(); + await initChangelog(); + await setupControlUI(); // reconnect server session await reconnectUI(); diff --git a/requirements.txt b/requirements.txt index d96a32b64..2cacf05d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -51,7 +51,6 @@ pandas==2.3.1 numba==0.61.2 protobuf==4.25.3 pytorch_lightning==2.5.4 -tokenizers==0.22.0 urllib3==1.26.19 Pillow==10.4.0 timm==1.0.16