mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-10 15:52:38 +02:00
perf: keep recent html and pdf viewers mounted to preserve state
This commit is contained in:
parent
a4cd6abb3a
commit
49a50279da
2 changed files with 74 additions and 10 deletions
|
|
@ -13,11 +13,10 @@ import { ChatInputWithMentions, type StagedAttachment } from './components/chat-
|
|||
import { ChatMessageAttachments } from '@/components/chat-message-attachments'
|
||||
import { GraphView, type GraphEdge, type GraphNode } from '@/components/graph-view';
|
||||
import { BasesView, type BaseConfig, DEFAULT_BASE_CONFIG } from '@/components/bases-view';
|
||||
import { HtmlFileViewer } from '@/components/html-file-viewer';
|
||||
import { ImageFileViewer } from '@/components/image-file-viewer';
|
||||
import { VideoFileViewer } from '@/components/video-file-viewer';
|
||||
import { PdfFileViewer } from '@/components/pdf-file-viewer';
|
||||
import { AudioFileViewer } from '@/components/audio-file-viewer';
|
||||
import { PersistentViewerCache } from '@/components/persistent-viewer-cache';
|
||||
import { useDebounce } from './hooks/use-debounce';
|
||||
import { SidebarContentPanel } from '@/components/sidebar-content';
|
||||
import { SuggestedTopicsView } from '@/components/suggested-topics-view';
|
||||
|
|
@ -4722,6 +4721,15 @@ function App() {
|
|||
/>
|
||||
</div>
|
||||
) : selectedPath ? (
|
||||
<>
|
||||
{/* Always-mounted persistent cache for HTML/PDF — hidden when active file is something else, so iframes preserve scroll/page/zoom across switches. */}
|
||||
<div
|
||||
className="flex-1 min-h-0 overflow-hidden"
|
||||
style={{ display: /\.(html?|pdf)$/i.test(selectedPath) ? 'block' : 'none' }}
|
||||
>
|
||||
<PersistentViewerCache activePath={selectedPath} />
|
||||
</div>
|
||||
{!/\.(html?|pdf)$/i.test(selectedPath) && (
|
||||
selectedPath.endsWith('.md') ? (
|
||||
<div className="flex-1 min-h-0 flex flex-row overflow-hidden">
|
||||
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
||||
|
|
@ -4831,10 +4839,6 @@ function App() {
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
) : selectedPath?.toLowerCase().endsWith('.html') || selectedPath?.toLowerCase().endsWith('.htm') ? (
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<HtmlFileViewer path={selectedPath} />
|
||||
</div>
|
||||
) : selectedPath && /\.(png|jpe?g|webp|gif|svg|avif|bmp|ico)$/i.test(selectedPath) ? (
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<ImageFileViewer path={selectedPath} />
|
||||
|
|
@ -4843,10 +4847,6 @@ function App() {
|
|||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<VideoFileViewer path={selectedPath} />
|
||||
</div>
|
||||
) : selectedPath?.toLowerCase().endsWith('.pdf') ? (
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<PdfFileViewer path={selectedPath} />
|
||||
</div>
|
||||
) : selectedPath && /\.(mp3|wav|m4a|ogg|flac|aac)$/i.test(selectedPath) ? (
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<AudioFileViewer path={selectedPath} />
|
||||
|
|
@ -4858,6 +4858,8 @@ function App() {
|
|||
</pre>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
) : selectedTask ? (
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<BackgroundTaskDetail
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { HtmlFileViewer } from './html-file-viewer'
|
||||
import { PdfFileViewer } from './pdf-file-viewer'
|
||||
|
||||
const CACHE_LIMIT = 5
|
||||
|
||||
function isCacheable(path: string): boolean {
|
||||
const lower = path.toLowerCase()
|
||||
return lower.endsWith('.html') || lower.endsWith('.htm') || lower.endsWith('.pdf')
|
||||
}
|
||||
|
||||
function renderViewer(path: string): JSX.Element | null {
|
||||
const lower = path.toLowerCase()
|
||||
if (lower.endsWith('.html') || lower.endsWith('.htm')) {
|
||||
return <HtmlFileViewer path={path} />
|
||||
}
|
||||
if (lower.endsWith('.pdf')) {
|
||||
return <PdfFileViewer path={path} />
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
interface PersistentViewerCacheProps {
|
||||
activePath: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps recently-opened HTML and PDF viewers mounted in the DOM,
|
||||
* toggling visibility instead of unmounting. This preserves iframe
|
||||
* state (PDF page/zoom, HTML scroll/JS state) across file switches.
|
||||
*/
|
||||
export function PersistentViewerCache({ activePath }: PersistentViewerCacheProps) {
|
||||
const [mountedPaths, setMountedPaths] = useState<string[]>(() =>
|
||||
isCacheable(activePath) ? [activePath] : []
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCacheable(activePath)) return
|
||||
setMountedPaths((prev) => {
|
||||
if (prev.includes(activePath)) {
|
||||
// Move to most-recent position
|
||||
return [...prev.filter((p) => p !== activePath), activePath]
|
||||
}
|
||||
const next = [...prev, activePath]
|
||||
return next.length > CACHE_LIMIT ? next.slice(-CACHE_LIMIT) : next
|
||||
})
|
||||
}, [activePath])
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full">
|
||||
{mountedPaths.map((p) => (
|
||||
<div
|
||||
key={p}
|
||||
className="absolute inset-0"
|
||||
style={{ display: p === activePath ? 'block' : 'none' }}
|
||||
>
|
||||
{renderViewer(p)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue