Tesseract / studio /src /components /PreviewPanel.jsx
rkbosamia's picture
Tesseract major version upgrade - v2
9c49532
Raw
History Blame Contribute Delete
3.84 kB
import { useMemo, useState } from 'react'
import useAppStore from '../store/appStore'
import { useProcesses } from '../hooks/useProcesses'
import PreviewFrame from './PreviewFrame'
import LogsPanel from './LogsPanel'
import DbBrowserPanel from './DbBrowserPanel'
import TerminalPanel from './TerminalPanel'
export default function PreviewPanel() {
const { runtime, project, templates } = useAppStore()
const { status } = useProcesses()
const [tab, setTab] = useState('preview') // 'preview' | 'logs' | 'db'
const isRunning = status === 'running'
// Resolve template's preview config (kind: frontend|backend, path).
// Default to frontend:/ for templates that pre-date the preview block.
const tplEntry = useMemo(
() => templates.find((t) => t.id === project.template),
[templates, project.template],
)
const preview = tplEntry?.preview ?? { kind: 'frontend', path: '/' }
const targetPort = preview.kind === 'backend'
? runtime.backendPort
: runtime.frontendPort
const iframeUrl = isRunning && targetPort
? `http://localhost:${targetPort}${preview.path || '/'}`
: null
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', background: 'var(--bg)' }}>
{/* ── Header β€” tabs only. Run / Stop / Reload / Open-in-browser all
live in TopBar so they're visible from any panel. ───────────────── */}
<div
style={{
display: 'flex',
alignItems: 'center',
padding: '0 10px',
height: 34,
flexShrink: 0,
borderBottom: '1px solid var(--border)',
background: 'var(--surface)',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 0 }}>
{['preview', 'logs', 'db', 'terminal'].map((t) => (
<button
key={t}
onClick={() => setTab(t)}
style={{
padding: '0 12px',
height: 34,
border: 'none',
borderBottom: tab === t ? '2px solid var(--accent)' : '2px solid transparent',
background: 'transparent',
color: tab === t ? 'var(--text)' : 'var(--text-dim)',
fontSize: 11,
fontWeight: tab === t ? 600 : 400,
cursor: 'pointer',
letterSpacing: '0.04em',
textTransform: 'uppercase',
transition: 'color 0.15s',
}}
>
{t}
</button>
))}
</div>
</div>
{/* ── Body ─────────────────────────────────────────────────────────── */}
<div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
{/* Preview tab β€” always rendered so iframe keeps its state when switching tabs */}
<div style={{ position: 'absolute', inset: 0, visibility: tab === 'preview' ? 'visible' : 'hidden' }}>
<PreviewFrame url={iframeUrl} />
</div>
{tab === 'logs' && (
<div style={{ position: 'absolute', inset: 0 }}>
<LogsPanel />
</div>
)}
{tab === 'db' && (
<div style={{ position: 'absolute', inset: 0 }}>
<DbBrowserPanel />
</div>
)}
{/* Terminal stays mounted once visited so the PTY session survives
tab switches. Rendered hidden when another tab is active. */}
{tab === 'terminal' && (
<div style={{ position: 'absolute', inset: 0 }}>
<TerminalPanel />
</div>
)}
</div>
</div>
)
}