mirror of
https://github.com/HeyPuter/puter.git
synced 2026-09-25 22:55:58 +00:00
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.
This commit is contained in:
@@ -29,7 +29,7 @@ function buildAppsGrid (apps) {
|
||||
return h;
|
||||
}
|
||||
|
||||
let h = '<div class="myapps-grid">';
|
||||
let h = '<div class="myapps-grid myapps-grid-loading">';
|
||||
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)
|
||||
: '<div class="myapps-empty"><p>No apps match your search</p></div>',
|
||||
);
|
||||
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('<div class="myapps-empty"><p>Failed to load apps</p></div>');
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user