';
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('
');
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 {