Listen for hnf:viewport postMessage — resize iframe to 1280px for desktop-forced pages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-21 09:00:37 +00:00
parent 9e5c415685
commit de49df70b0

View file

@ -9,6 +9,7 @@ export function AppFrame({ user }: { user: User }) {
const navigate = useNavigate() const navigate = useNavigate()
const app = user.apps.find(a => a.slug === slug) const app = user.apps.find(a => a.slug === slug)
const [menuOpen, setMenuOpen] = useState(false) const [menuOpen, setMenuOpen] = useState(false)
const [desktopFrame, setDesktopFrame] = useState(false)
const iframeRef = useRef<HTMLIFrameElement>(null) const iframeRef = useRef<HTMLIFrameElement>(null)
// Capture hash once at mount — before iframe navigation overwrites it // Capture hash once at mount — before iframe navigation overwrites it
@ -18,11 +19,22 @@ export function AppFrame({ user }: { user: User }) {
return sub && sub !== '/' ? `${app.base_path}${sub}` : `${app.base_path}/` return sub && sub !== '/' ? `${app.base_path}${sub}` : `${app.base_path}/`
}) })
// Clear hash when switching apps // Clear hash and reset desktop frame when switching apps
useEffect(() => { useEffect(() => {
setDesktopFrame(false)
return () => { history.replaceState(null, '', window.location.pathname) } return () => { history.replaceState(null, '', window.location.pathname) }
}, [slug]) }, [slug])
// Listen for viewport mode requests from embedded apps
useEffect(() => {
function handleMessage(e: MessageEvent) {
if (e.data?.type !== 'hnf:viewport') return
setDesktopFrame(e.data.mode === 'desktop')
}
window.addEventListener('message', handleMessage)
return () => window.removeEventListener('message', handleMessage)
}, [])
// Auto-close when user clicks into / navigates within the iframe // Auto-close when user clicks into / navigates within the iframe
useEffect(() => { useEffect(() => {
if (!menuOpen) return if (!menuOpen) return
@ -32,6 +44,8 @@ export function AppFrame({ user }: { user: User }) {
}, [menuOpen]) }, [menuOpen])
function handleIframeLoad() { function handleIframeLoad() {
// Each page declares its own viewport preference on mount; reset between navigations
setDesktopFrame(false)
try { try {
const iframePath = iframeRef.current?.contentWindow?.location.pathname ?? '' const iframePath = iframeRef.current?.contentWindow?.location.pathname ?? ''
const relative = iframePath.replace(app!.base_path, '') || '/' const relative = iframePath.replace(app!.base_path, '') || '/'
@ -97,13 +111,13 @@ export function AppFrame({ user }: { user: User }) {
)} )}
{/* App iframe */} {/* App iframe */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}> <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflowX: desktopFrame ? 'auto' : 'hidden', overflowY: 'hidden' }}>
<div style={{ height: '3px', background: app.theme_color, flexShrink: 0 }} /> <div style={{ height: '3px', background: app.theme_color, flexShrink: 0 }} />
<iframe <iframe
ref={iframeRef} ref={iframeRef}
src={initialSrc} src={initialSrc}
title={app.name} title={app.name}
style={{ flex: 1, border: 'none', width: '100%' }} style={{ flex: 1, border: 'none', width: desktopFrame ? '1280px' : '100%', minWidth: desktopFrame ? '1280px' : undefined }}
allow="clipboard-read; clipboard-write" allow="clipboard-read; clipboard-write"
onLoad={handleIframeLoad} onLoad={handleIframeLoad}
/> />