diff --git a/src/gui/src/UI/Dashboard/TabApps.js b/src/gui/src/UI/Dashboard/TabApps.js index 4c179503c..9772251b8 100644 --- a/src/gui/src/UI/Dashboard/TabApps.js +++ b/src/gui/src/UI/Dashboard/TabApps.js @@ -73,7 +73,7 @@ function buildTileHtml (app) { const { title, targetLink } = resolveTileDisplay(app); const iconUrl = app.iconUrl || window.icons['app.svg']; - let h = `
`; + let h = `
`; h += '
'; h += ``; h += '
'; @@ -127,21 +127,13 @@ function buildPagerHtml (apps, layout, instant) { return h; } -function showUninstallModal ({ appName, appTitle, appUid, removesTile, self, $el_window }) { +function showUninstallModal ({ appName, appTitle, appUid, self, $el_window }) { const displayName = (appTitle || appName || '').trim(); - // A recommended/recent app's tile reappears on the next load even after a - // successful revoke; say so up front so the uninstall doesn't look like it - // silently failed. (Recommended apps stay indefinitely; recent ones until - // they age out of the open history — don't claim more than that.) - const note = removesTile - ? '' - : '

Its icon will stay in Apps for now, because this app is in your recommended or recently used list.

'; const $overlay = $(`

Uninstall ${html_encode(displayName)}?

This will revoke all permissions for this app.

- ${note}
@@ -172,22 +164,15 @@ function showUninstallModal ({ appName, appTitle, appUid, removesTile, self, $el try { await puter.perms.revokeApp(appUid, '*'); // A load fetched before the revoke must not apply — it would - // resurrect the pre-revoke grid. + // resurrect the pre-revoke grid. No refetch here either: the + // recommended/recent launch lists don't know about the revoke, + // so an immediate reload would just re-add the tile. The saved + // order intentionally keeps the app's name: reconcileAppOrder + // ignores it while the app is gone and restores its position if + // it comes back. self._invalidateInFlightLoads(); - // Drop the tile right away when we expect the uninstall to stick - // (immediate feedback) — but whether it really disappears depends - // on server-side state (recommended/recent lists) that load-time - // flags can only guess at, so refetch either way and let the grid - // converge to the truth. The saved order intentionally keeps the - // app's name: reconcileAppOrder ignores it while the app is gone - // and restores its position if it comes back. - if ( removesTile ) { - self._apps = self._apps.filter(a => a.name !== appName); - self.renderApps($el_window, { preservePage: true, instant: true }); - } - // Keep the user's page and skip the load fade — this is a - // background sync, not a fresh visit. - self.loadApps($el_window, { preservePage: true, instant: true }); + self._apps = self._apps.filter(a => a.name !== appName); + self.renderApps($el_window, { preservePage: true, instant: true }); } catch ( err ) { console.error('Failed to uninstall app:', err); } @@ -316,12 +301,6 @@ const TabApps = { const appName = $(this).attr('data-app-name'); const appTitle = $(this).attr('data-app-title'); const appUid = $(this).attr('data-app-uid'); - // Whether the tile disappears for good on uninstall (see - // _fetchAndRenderApps). Uninstall is still offered when it won't — - // it is the only UI path that revokes an app's permissions, and - // recommended/recent apps need that path too; the modal just sets - // the expectation that their tile stays. - const removesTile = $(this).attr('data-app-uninstallable') === '1'; const noUninstall = APP_NAMES_NO_UNINSTALL.has((appName || '').toLowerCase()); const items = noUninstall @@ -334,7 +313,6 @@ const TabApps = { appName, appTitle, appUid, - removesTile, self, $el_window, }); @@ -982,7 +960,7 @@ const TabApps = { } }, - loadApps ($el_window, renderOpts) { + loadApps ($el_window) { if ( this._drag ) { // Don't fetch/re-render on top of a live drag; cancel a pending // (not-yet-started) pickup so a rebuild can't strand it. @@ -992,14 +970,14 @@ const TabApps = { // init and the initial-route onActivate both fire on open; join the // in-flight load instead of issuing a duplicate request trio. if ( this._loadPromise ) return this._loadPromise; - const p = this._fetchAndRenderApps($el_window, renderOpts).finally(() => { + const p = this._fetchAndRenderApps($el_window).finally(() => { if ( this._loadPromise === p ) this._loadPromise = null; }); this._loadPromise = p; return p; }, - async _fetchAndRenderApps ($el_window, renderOpts = {}) { + async _fetchAndRenderApps ($el_window) { // Give each load a monotonically increasing id. An older/slower // response must not clobber a newer one that already applied — or a // reorder the user saved while a stale fetch was in flight. We gate on @@ -1082,29 +1060,6 @@ const TabApps = { iconUrl: app.iconUrl || app.icon || null, })); - // Whether an uninstall makes the tile disappear for good: apps the - // user actually installed that are in NEITHER launch list. - // revokeApp still revokes permissions for the others, but - // get-launch-apps re-adds their tiles on the next load — the - // recommended list is hardcoded and the recent list is built from - // app-open history, which a revoke doesn't touch. The context menu - // uses this flag to set expectations in the modal (see - // showUninstallModal), not to withhold the action. - const recommendedNames = new Set( - (launchData.recommended || []).map(a => a.name), - ); - const recentNames = new Set( - (launchData.recent || []).map(a => a.name), - ); - const installedNames = new Set( - (Array.isArray(installedApps) ? installedApps : []).map(a => a.name), - ); - const isUninstallable = name => - installedNames.has(name) - && ! recommendedNames.has(name) - && ! recentNames.has(name) - && ! APP_NAMES_NO_UNINSTALL.has((name || '').toLowerCase()); - // Build seen set from launch apps const seen = new Set(); const merged = []; @@ -1112,22 +1067,22 @@ const TabApps = { for ( const app of launchApps ) { if ( seen.has(app.name) ) continue; seen.add(app.name); - merged.push({ ...app, uninstallable: isUninstallable(app.name) }); + merged.push(app); } // Append installed apps that aren't already in the list for ( const app of installedApps ) { if ( seen.has(app.name) ) continue; seen.add(app.name); - merged.push({ ...app, uninstallable: isUninstallable(app.name) }); + merged.push(app); } // A page beyond the first failed: fill the gap with apps we - // already know about (keeping their previous uninstallable flags) - // so one flaky request among N can't make apps vanish from the - // grid, from search, or from a subsequently saved order. Apps - // uninstalled remotely may linger until the next complete - // refresh — the same staleness any between-refresh window has. + // already know about so one flaky request among N can't make + // apps vanish from the grid, from search, or from a subsequently + // saved order. Apps uninstalled remotely may linger until the + // next complete refresh — the same staleness any between-refresh + // window has. if ( ! installedResult.complete && Array.isArray(this._apps) ) { for ( const app of this._apps ) { if ( seen.has(app.name) ) continue; @@ -1170,7 +1125,7 @@ const TabApps = { this._hasCustomOrder = Array.isArray(effectiveOrder) && effectiveOrder.length > 0; this._apps = reconcileAppOrder(merged, effectiveOrder); - this.renderApps($el_window, renderOpts); + this.renderApps($el_window); } catch (e) { console.error('Failed to load installed apps:', e); // Only show the failure placeholder when nothing has loaded yet; a