From e1e2cad31d524ba1817d6d77a33cec73731a0ab3 Mon Sep 17 00:00:00 2001 From: jelveh Date: Thu, 26 Mar 2026 20:29:35 -0700 Subject: [PATCH] Hide apps grid until icons finish loading Prevent a visual flash by hiding the apps grid until all app icons have loaded. Adds revealWhenLoaded($container) which removes the myapps-grid-loading class when images finish (or error). TabApps now caches the .myapps-container lookup and calls revealWhenLoaded after initial render, after search results, and after loading installed apps. CSS changes: add .myapps-grid-loading (opacity:0 + transition) and a small padding-right tweak for the search input. --- src/gui/src/UI/Dashboard/TabApps.js | 41 ++++++++++++++++++++++++++--- src/gui/src/css/dashboard.css | 7 +++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/gui/src/UI/Dashboard/TabApps.js b/src/gui/src/UI/Dashboard/TabApps.js index 2262a585b..7cc1e5e52 100644 --- a/src/gui/src/UI/Dashboard/TabApps.js +++ b/src/gui/src/UI/Dashboard/TabApps.js @@ -29,7 +29,7 @@ function buildAppsGrid (apps) { return h; } - let h = '
'; + let h = '
'; for ( const app of apps ) { const title = (app.title || app.name || '').trim(); const iconUrl = app.iconUrl || window.icons['app.svg']; @@ -45,6 +45,36 @@ function buildAppsGrid (apps) { return h; } +function revealWhenLoaded ($container) { + const $grid = $container.find('.myapps-grid-loading'); + if ( $grid.length === 0 ) return; + + const imgs = $grid.find('img').toArray(); + if ( imgs.length === 0 ) { + $grid.removeClass('myapps-grid-loading'); + return; + } + + let loaded = 0; + const total = imgs.length; + + function onDone () { + loaded++; + if ( loaded >= total ) { + $grid.removeClass('myapps-grid-loading'); + } + } + + for ( const img of imgs ) { + if ( img.complete ) { + onDone(); + } else { + img.addEventListener('load', onDone, { once: true }); + img.addEventListener('error', onDone, { once: true }); + } + } +} + const TabApps = { id: 'apps', label: 'Apps', @@ -80,8 +110,11 @@ const TabApps = { if ( ! self._apps ) return; + const $container = $el_window.find('.myapps-container'); + if ( ! query ) { - $el_window.find('.myapps-container').html(buildAppsGrid(self._apps)); + $container.html(buildAppsGrid(self._apps)); + revealWhenLoaded($container); return; } @@ -91,11 +124,12 @@ const TabApps = { return title.includes(query) || name.includes(query); }); - $el_window.find('.myapps-container').html( + $container.html( filtered.length > 0 ? buildAppsGrid(filtered) : '

No apps match your search

', ); + revealWhenLoaded($container); } $el_window.on('input', '.myapps-search', function () { @@ -174,6 +208,7 @@ const TabApps = { this._apps = merged; $container.html(buildAppsGrid(merged)); + revealWhenLoaded($container); } catch (e) { console.error('Failed to load installed apps:', e); $container.html('

Failed to load apps

'); diff --git a/src/gui/src/css/dashboard.css b/src/gui/src/css/dashboard.css index 2e2929279..e593ba3af 100644 --- a/src/gui/src/css/dashboard.css +++ b/src/gui/src/css/dashboard.css @@ -478,6 +478,7 @@ body { color: var(--dashboard-text-primary); outline: none; transition: border-color 0.15s; + padding-right: 35px; } .myapps-search:focus { @@ -525,6 +526,12 @@ body { grid-template-columns: repeat(auto-fill, 80px); gap: 24px 20px; justify-content: start; + opacity: 1; + transition: opacity 0.2s ease; +} + +.myapps-grid-loading { + opacity: 0; } .myapps-tile {