refactor: extract getViewerType helper to share extension list

This commit is contained in:
Gagancreates 2026-05-08 16:45:47 +05:30
parent 60e5b2cbc7
commit 385ed3377f
3 changed files with 70 additions and 21 deletions

View file

@ -17,6 +17,7 @@ import { ImageFileViewer } from '@/components/image-file-viewer';
import { VideoFileViewer } from '@/components/video-file-viewer';
import { AudioFileViewer } from '@/components/audio-file-viewer';
import { PersistentViewerCache } from '@/components/persistent-viewer-cache';
import { getViewerType, isMediaPath, isCacheableViewerPath } from '@/lib/file-types';
import { useDebounce } from './hooks/use-debounce';
import { SidebarContentPanel } from '@/components/sidebar-content';
import { SuggestedTopicsView } from '@/components/suggested-topics-view';
@ -1428,10 +1429,10 @@ function App() {
}
const requestId = (fileLoadRequestIdRef.current += 1)
const pathToLoad = selectedPath
// Media viewers (HTML, image, video, PDF) self-load via app:// protocol.
// Media viewers (HTML, image, video, audio, PDF) self-load via app:// protocol.
// Skip the generic UTF-8 loader so we don't trash fileContent with binary
// bytes or double-fetch large files.
if (/\.(html?|png|jpe?g|webp|gif|svg|avif|bmp|ico|mp4|mov|webm|m4v|pdf|mp3|wav|m4a|ogg|flac|aac)$/i.test(pathToLoad)) {
if (isMediaPath(pathToLoad)) {
setFileContent('')
return
}
@ -4725,11 +4726,11 @@ function App() {
{/* 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' }}
style={{ display: isCacheableViewerPath(selectedPath) ? 'block' : 'none' }}
>
<PersistentViewerCache activePath={selectedPath} />
</div>
{!/\.(html?|pdf)$/i.test(selectedPath) && (
{!isCacheableViewerPath(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">
@ -4839,15 +4840,15 @@ function App() {
/>
)}
</div>
) : selectedPath && /\.(png|jpe?g|webp|gif|svg|avif|bmp|ico)$/i.test(selectedPath) ? (
) : selectedPath && getViewerType(selectedPath) === 'image' ? (
<div className="flex-1 min-h-0 overflow-hidden">
<ImageFileViewer path={selectedPath} />
</div>
) : selectedPath && /\.(mp4|mov|webm|m4v)$/i.test(selectedPath) ? (
) : selectedPath && getViewerType(selectedPath) === 'video' ? (
<div className="flex-1 min-h-0 overflow-hidden">
<VideoFileViewer path={selectedPath} />
</div>
) : selectedPath && /\.(mp3|wav|m4a|ogg|flac|aac)$/i.test(selectedPath) ? (
) : selectedPath && getViewerType(selectedPath) === 'audio' ? (
<div className="flex-1 min-h-0 overflow-hidden">
<AudioFileViewer path={selectedPath} />
</div>

View file

@ -1,22 +1,14 @@
import { useEffect, useState } from 'react'
import { HtmlFileViewer } from './html-file-viewer'
import { PdfFileViewer } from './pdf-file-viewer'
import { getViewerType, isCacheableViewerPath } from '@/lib/file-types'
const CACHE_LIMIT = 3
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} />
}
const type = getViewerType(path)
if (type === 'html') return <HtmlFileViewer path={path} />
if (type === 'pdf') return <PdfFileViewer path={path} />
return null
}
@ -31,11 +23,11 @@ interface PersistentViewerCacheProps {
*/
export function PersistentViewerCache({ activePath }: PersistentViewerCacheProps) {
const [mountedPaths, setMountedPaths] = useState<string[]>(() =>
isCacheable(activePath) ? [activePath] : []
isCacheableViewerPath(activePath) ? [activePath] : []
)
useEffect(() => {
if (!isCacheable(activePath)) return
if (!isCacheableViewerPath(activePath)) return
setMountedPaths((prev) => {
// Never reorder existing entries — moving a keyed iframe in the DOM
// detaches it, which causes the browser to re-navigate (state lost).

View file

@ -0,0 +1,56 @@
/**
* Single source of truth for which file types the knowledge viewer renders.
*
* Both the App.tsx loader-skip check and the render-switch consume this so
* adding a new extension is a one-place edit. The persistent-viewer-cache
* also uses it to decide what to keep mounted.
*/
export type ViewerType = 'html' | 'image' | 'video' | 'audio' | 'pdf'
const VIEWER_BY_EXT: Record<string, ViewerType> = {
html: 'html',
htm: 'html',
png: 'image',
jpg: 'image',
jpeg: 'image',
webp: 'image',
gif: 'image',
svg: 'image',
avif: 'image',
bmp: 'image',
ico: 'image',
mp4: 'video',
mov: 'video',
webm: 'video',
m4v: 'video',
mp3: 'audio',
wav: 'audio',
m4a: 'audio',
ogg: 'audio',
flac: 'audio',
aac: 'audio',
pdf: 'pdf',
}
function extensionOf(path: string): string {
const lower = path.toLowerCase()
const dot = lower.lastIndexOf('.')
return dot >= 0 ? lower.slice(dot + 1) : ''
}
/** Returns the viewer type for a path, or null if no media viewer handles it. */
export function getViewerType(path: string): ViewerType | null {
return VIEWER_BY_EXT[extensionOf(path)] ?? null
}
/** True if the path is rendered by one of the dedicated media viewers. */
export function isMediaPath(path: string): boolean {
return getViewerType(path) !== null
}
/** True if the viewer for this path participates in the persistent mount cache. */
export function isCacheableViewerPath(path: string): boolean {
const t = getViewerType(path)
return t === 'html' || t === 'pdf'
}