fix(apps): repair netbird install and app icons

This commit is contained in:
archipelago
2026-05-19 17:20:32 -04:00
parent cede77f3bc
commit f0bd49d03d
11 changed files with 75 additions and 14 deletions

View File

@@ -2098,7 +2098,7 @@ html:has(body.video-background-active)::before {
position: relative;
width: 60px;
height: 60px;
border-radius: 14px;
border-radius: 18px;
overflow: visible;
background: rgba(255, 255, 255, 0.08);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
@@ -2108,7 +2108,16 @@ html:has(body.video-background-active)::before {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 14px;
border-radius: 18px;
}
.app-card-icon {
border-radius: 16px;
}
.app-detail-icon {
border-radius: 22px;
object-fit: cover;
}
/* Status dot — top-right of icon */
@@ -2140,7 +2149,7 @@ html:has(body.video-background-active)::before {
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.6);
border-radius: 14px;
border-radius: 18px;
}
.app-icon-label {

View File

@@ -158,7 +158,7 @@ const { t } = useI18n()
const appId = computed(() => {
const id = route.params.id
if (typeof id !== 'string' || !/^[a-z0-9][a-z0-9._-]*$/.test(id) || id.length > 64) {
router.replace('/apps')
router.replace('/dashboard/apps')
return ''
}
return id

View File

@@ -138,7 +138,7 @@ const displayMode = ref<DisplayMode>(
const appId = computed(() => {
const id = props.appIdProp || (route.params.appId as string)
if (typeof id !== 'string' || !/^[a-z0-9][a-z0-9._-]*$/.test(id) || id.length > 64) {
router.replace('/apps')
router.replace('/dashboard/apps')
return ''
}
return id
@@ -146,7 +146,7 @@ const appId = computed(() => {
const appTitle = computed(() => resolveAppTitle(appId.value))
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768
const mustOpenNewTab = computed(() => !isMobile && NEW_TAB_APPS.has(appId.value))
const mustOpenNewTab = computed(() => NEW_TAB_APPS.has(appId.value))
const screensaverReason = computed(() => `app-session:${appId.value}`)
const screensaverSuppressedApps = new Set([
'indeedhub',
@@ -197,7 +197,11 @@ function setMode(mode: DisplayMode) {
if (!isInlinePanel.value && mode === 'panel') {
const id = appId.value
const launcher = useAppLauncherStore()
router.push({ name: 'apps' }).then(() => {
const fallback = route.query.returnTo
const fallbackPath = typeof fallback === 'string' && fallback.startsWith('/dashboard')
? fallback
: '/dashboard/apps'
router.push(fallbackPath).then(() => {
launcher.panelAppId = id
})
return
@@ -337,8 +341,9 @@ watch(displayMode, (mode) => {
})
onMounted(() => {
// Apps that block iframes (X-Frame-Options) -- open in new tab, close session
if (mustOpenNewTab.value && appUrl.value) {
// Desktop apps that block iframes open externally. Mobile keeps the user in
// Archipelago and shows the explicit fallback instead of leaving the shell.
if (!isMobile && mustOpenNewTab.value && appUrl.value) {
window.open(appUrl.value, '_blank', 'noopener,noreferrer')
if (isInlinePanel.value) emit('close')
else closeRouteSession()

View File

@@ -5,7 +5,7 @@
<img
:src="icon"
:alt="pkg.manifest.title"
class="w-20 h-20 rounded-xl shadow-xl flex-shrink-0"
class="app-detail-icon w-20 h-20 shadow-xl flex-shrink-0"
@error="handleImageError"
/>
@@ -119,7 +119,7 @@
<img
:src="icon"
:alt="pkg.manifest.title"
class="w-20 h-20 rounded-xl shadow-xl flex-shrink-0"
class="app-detail-icon w-20 h-20 shadow-xl flex-shrink-0"
@error="handleImageError"
/>

View File

@@ -31,7 +31,7 @@
<img
:src="icon"
:alt="title"
class="w-14 h-14 rounded-lg object-cover bg-white/10"
class="app-card-icon w-14 h-14 object-cover bg-white/10"
@error="handleImageError"
/>
<div class="flex-1 min-w-0 overflow-hidden">

View File

@@ -35,6 +35,11 @@ describe('AppIconGrid', () => {
setActivePinia(createPinia())
vi.clearAllMocks()
localStorage.clear()
Object.defineProperty(window, 'innerWidth', {
value: 1024,
writable: true,
configurable: true,
})
Object.defineProperty(window, 'location', {
value: { hostname: '192.168.1.198' },
writable: true,
@@ -55,4 +60,23 @@ describe('AppIconGrid', () => {
expect(mockWindowOpen).not.toHaveBeenCalled()
expect(useAppLauncherStore().panelAppId).toBe('lnd')
})
it('opens desktop new-tab apps through app session on mobile', async () => {
Object.defineProperty(window, 'innerWidth', {
value: 390,
writable: true,
configurable: true,
})
const wrapper = mount(AppIconGrid, {
props: { apps: [['gitea', makePkg('gitea')]] },
global: {
plugins: [createPinia()],
},
})
await wrapper.get('.app-icon-item').trigger('click')
expect(mockWindowOpen).not.toHaveBeenCalled()
})
})

View File

@@ -166,7 +166,8 @@ const APP_ICON_FALLBACKS: Record<string, string> = {
}
export function resolveAppIcon(id: string, pkg: PackageDataEntry, curatedIcon?: string): string {
const icon = (pkg["static-files"]?.icon || "").trim()
const rawIcon = (pkg["static-files"]?.icon || "").trim()
const icon = rawIcon === '/assets/img/favico.png' ? '' : rawIcon
if (
icon.startsWith("/") ||
icon.startsWith("http://") ||