feat(neode-ui): mobile app-launch UX — store-driven panel, loader, ElectrumX icon
- Mobile launches use the store-driven panel (no route push) so the background tab no longer changes and closing returns to where you launched from. - Tab-only apps open directly (in-app WebView on companion / new tab on PWA) — no "this app opens in a tab" interstitial. - Shared AppLoadingScreen (app icon + progress bar) on the app session and the legacy iframe overlay instead of a black screen. - Pin the dashboard to 100dvh on mobile so the mesh chat/tools panes stop sliding under the bottom tab bar in mobile browsers (no-op in the companion WebView). - ElectrumX/electrs/electrs-ui ids now resolve to the real ElectrumX icon in My Apps. - isMobile made reactive so overlay/footer/teleport decisions track the viewport. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
db7d424bff
commit
a7c7c44843
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="app-session-root">
|
||||
<Teleport to="body" :disabled="isInlinePanel">
|
||||
<Teleport to="body" :disabled="isInlinePanel && !isMobile">
|
||||
<div
|
||||
:class="backdropClasses"
|
||||
@click.self="handleBackdropClick"
|
||||
@@ -27,6 +27,7 @@
|
||||
:app-url="appUrl"
|
||||
:app-id="appId"
|
||||
:app-title="appTitle"
|
||||
:app-icon="appIcon"
|
||||
:loading="loading"
|
||||
:iframe-blocked="iframeBlocked"
|
||||
:must-open-new-tab="mustOpenNewTab"
|
||||
@@ -104,10 +105,10 @@ import {
|
||||
type DisplayMode, DISPLAY_MODE_KEY, NEW_TAB_APPS, IFRAME_BLOCKED_APPS,
|
||||
resolveAppUrl, resolveAppTitle,
|
||||
} from './appSession/appSessionConfig'
|
||||
import { launchBlockedReason } from './apps/appsConfig'
|
||||
import { launchBlockedReason, resolveAppIcon } from './apps/appsConfig'
|
||||
import { useAppIdentity } from './appSession/useAppIdentity'
|
||||
import { useNostrBridge } from './appSession/useNostrBridge'
|
||||
import { openExternalUrl } from '@/utils/openExternal'
|
||||
import { openExternalUrl, openInAppOrNewTab } from '@/utils/openExternal'
|
||||
import { useElectrsSync } from '@/composables/useElectrsSync'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -154,9 +155,17 @@ const appId = computed(() => {
|
||||
|
||||
const appTitle = computed(() => resolveAppTitle(appId.value))
|
||||
const packageEntry = computed(() => store.data?.['package-data']?.[appId.value] || null)
|
||||
const appIcon = computed(() =>
|
||||
packageEntry.value
|
||||
? resolveAppIcon(appId.value, packageEntry.value)
|
||||
: `/assets/img/app-icons/${appId.value}.png`
|
||||
)
|
||||
const blockedReason = computed(() => launchBlockedReason(appId.value, packageEntry.value))
|
||||
const blockedTitle = computed(() => appId.value === 'fedimint' || appId.value === 'fedimintd' ? 'Waiting for Bitcoin sync' : 'App not ready')
|
||||
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768
|
||||
// Reactive so the overlay/teleport/footer/animation decisions track the live
|
||||
// viewport (and match the CSS `md` breakpoint) instead of a stale one-shot read.
|
||||
const isMobile = ref(typeof window !== 'undefined' && window.innerWidth < 768)
|
||||
function updateIsMobile() { isMobile.value = window.innerWidth < 768 }
|
||||
const mustOpenNewTab = computed(() => NEW_TAB_APPS.has(appId.value))
|
||||
|
||||
// ElectrumX shows a sync screen before its real UI (the Electrum server only
|
||||
@@ -241,16 +250,18 @@ function setMode(mode: DisplayMode) {
|
||||
}
|
||||
}
|
||||
|
||||
// Reactive classes based on display mode
|
||||
// Reactive classes based on display mode. On mobile the store-driven panel
|
||||
// renders as a full-screen overlay (teleported to body) so it covers the nav
|
||||
// and the underlying page never changes — desktop keeps the inline panel.
|
||||
const backdropClasses = computed(() => {
|
||||
if (isInlinePanel.value) return 'app-session-backdrop-inline'
|
||||
if (isInlinePanel.value && !isMobile.value) return 'app-session-backdrop-inline'
|
||||
return 'app-session-backdrop-overlay'
|
||||
})
|
||||
|
||||
const panelClasses = computed(() => {
|
||||
const base = 'app-session-panel glass-card'
|
||||
if (isInlinePanel.value) return `${base} app-session-inline`
|
||||
if (displayMode.value === 'fullscreen') return `${base} app-session-fullscreen`
|
||||
if (isInlinePanel.value && !isMobile.value) return `${base} app-session-inline`
|
||||
if (displayMode.value === 'fullscreen' && !isMobile.value) return `${base} app-session-fullscreen`
|
||||
return `${base} app-session-overlay`
|
||||
})
|
||||
|
||||
@@ -370,10 +381,13 @@ watch(displayMode, (mode) => {
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// Apps that block iframes open externally on desktop. On mobile, keep the
|
||||
// session surface visible so launcher taps do not bounce straight out.
|
||||
if (mustOpenNewTab.value && appUrl.value && !isMobile) {
|
||||
window.open(appUrl.value, '_blank', 'noopener,noreferrer')
|
||||
// Apps that block iframes (X-Frame-Options) can't be shown in the session.
|
||||
// Open them directly instead of showing a "this app opens in a tab"
|
||||
// interstitial: desktop → new browser tab; mobile → in-app WebView (companion)
|
||||
// or new tab (PWA). Then dismiss the (empty) session surface.
|
||||
if (mustOpenNewTab.value && appUrl.value) {
|
||||
if (isMobile.value) openInAppOrNewTab(appUrl.value)
|
||||
else window.open(appUrl.value, '_blank', 'noopener,noreferrer')
|
||||
if (isInlinePanel.value) emit('close')
|
||||
else closeRouteSession()
|
||||
return
|
||||
@@ -381,8 +395,9 @@ onMounted(() => {
|
||||
|
||||
window.addEventListener('keydown', onKeyDown, true)
|
||||
window.addEventListener('message', onMessage)
|
||||
window.addEventListener('resize', updateIsMobile)
|
||||
document.addEventListener('fullscreenchange', onFullscreenChange)
|
||||
if (IFRAME_BLOCKED_APPS.has(appId.value) || (mustOpenNewTab.value && isMobile)) {
|
||||
if (IFRAME_BLOCKED_APPS.has(appId.value)) {
|
||||
loading.value = false
|
||||
iframeBlocked.value = true
|
||||
} else {
|
||||
@@ -404,6 +419,7 @@ onBeforeUnmount(() => {
|
||||
if (iframeCheckId) clearTimeout(iframeCheckId)
|
||||
window.removeEventListener('keydown', onKeyDown, true)
|
||||
window.removeEventListener('message', onMessage)
|
||||
window.removeEventListener('resize', updateIsMobile)
|
||||
document.removeEventListener('fullscreenchange', onFullscreenChange)
|
||||
screensaverStore.resume(screensaverReason.value)
|
||||
if (document.fullscreenElement) document.exitFullscreen().catch(() => {})
|
||||
|
||||
Reference in New Issue
Block a user