@@ -10,6 +10,7 @@ import { RiFullscreenExitLine, RiFullscreenLine } from "@remixicon/react"
1010import { PdfViewer } from "@/components/object/pdf-viewer"
1111import { ParquetViewer } from "@/components/object/parquet-viewer"
1212import { TiffViewer } from "@/components/object/tiff-viewer"
13+ import { getObjectPreviewMode , normalizePreviewContentType } from "@/lib/object-preview"
1314import Image from "next/image"
1415
1516const SAFE_TEXT_MIMES = [
@@ -27,8 +28,6 @@ const SAFE_TEXT_EXTENSIONS = [".txt", ".json", ".jsonl", ".ndjson", ".xml", ".cs
2728const SAFE_IMAGE_EXTENSIONS = [ ".png" , ".jpg" , ".jpeg" , ".gif" , ".webp" , ".bmp" , ".ico" , ".tif" , ".tiff" ]
2829const ALLOWED_SIZE = 1024 * 1024 * 2 // 2MB
2930
30- type PreviewMode = "text" | "image" | "pdf" | "parquet" | "sandbox" | "download" | "tiff"
31-
3231const PARQUET_MIMES = [ "application/vnd.apache.parquet" , "application/x-parquet" , "application/parquet" ]
3332const PARQUET_EXTENSIONS = [ ".parquet" , ".pq" ]
3433
@@ -52,10 +51,6 @@ type FullscreenElement = HTMLElement & {
5251 webkitRequestFullscreen ?: ( ) => Promise < void > | void
5352}
5453
55- function normalizeContentType ( contentType : string ) {
56- return contentType . split ( ";" ) [ 0 ] ?. trim ( ) . toLowerCase ( ) ?? ""
57- }
58-
5954function isSafeTextPreview ( contentType : string , objectKey : string , objectSize : number ) {
6055 if ( objectSize > ALLOWED_SIZE ) return false
6156 if ( SAFE_TEXT_MIMES . includes ( contentType ) ) return true
@@ -71,12 +66,6 @@ function isImagePreview(contentType: string, objectKey: string) {
7166 return SAFE_IMAGE_EXTENSIONS . some ( ( ext ) => keyLower . endsWith ( ext ) )
7267}
7368
74- function getPreviewMode ( hasPreviewUrl : boolean , canRenderText : boolean , canRenderImage : boolean ) : PreviewMode {
75- if ( ! hasPreviewUrl ) return "download"
76- if ( canRenderImage ) return "image"
77- return canRenderText ? "text" : "sandbox"
78- }
79-
8069function isPdfPreview ( contentType : string ) {
8170 return contentType === "application/pdf"
8271}
@@ -124,6 +113,7 @@ export function ObjectPreviewModal({ show, onShowChange, object }: ObjectPreview
124113 const { t } = useTranslation ( )
125114 const [ textContent , setTextContent ] = React . useState ( "" )
126115 const [ loading , setLoading ] = React . useState ( false )
116+ const [ audioLoadError , setAudioLoadError ] = React . useState ( false )
127117 const [ isFormatted , setIsFormatted ] = React . useState ( true )
128118 const [ imageNaturalSize , setImageNaturalSize ] = React . useState < { width : number ; height : number } | null > ( null )
129119 const [ imageFitScale , setImageFitScale ] = React . useState ( 1 )
@@ -139,21 +129,23 @@ export function ObjectPreviewModal({ show, onShowChange, object }: ObjectPreview
139129 const objectSize = Number ( object ?. ContentLength ?? 0 )
140130 const objectKey = object ?. Key ?? ""
141131 const objectKeyLower = objectKey . toLowerCase ( )
142- const normalizedContentType = normalizeContentType ( contentType )
132+ const normalizedContentType = normalizePreviewContentType ( contentType )
143133
144134 const isJson = normalizedContentType === "application/json" || objectKeyLower . endsWith ( ".json" )
145135 const canRenderText = hasPreviewUrl && isSafeTextPreview ( normalizedContentType , objectKey , objectSize )
146136 const canRenderImage = hasPreviewUrl && isImagePreview ( normalizedContentType , objectKey )
147137 const canRenderPdf = hasPreviewUrl && isPdfPreview ( normalizedContentType )
148138 const canRenderParquet = hasPreviewUrl && isParquetPreview ( normalizedContentType , objectKey )
149139 const canRenderTiff = hasPreviewUrl && isTiffPreview ( objectKey )
150- const previewMode : PreviewMode = canRenderParquet
151- ? "parquet"
152- : canRenderPdf
153- ? "pdf"
154- : canRenderTiff
155- ? "tiff"
156- : getPreviewMode ( hasPreviewUrl , canRenderText , canRenderImage )
140+ const previewMode = getObjectPreviewMode ( {
141+ hasPreviewUrl,
142+ contentType,
143+ canRenderText,
144+ canRenderImage,
145+ canRenderPdf,
146+ canRenderParquet,
147+ canRenderTiff,
148+ } )
157149 const isImageMode = previewMode === "image"
158150 const isSelfScrollMode = isImageMode || previewMode === "parquet" || previewMode === "tiff"
159151
@@ -193,6 +185,10 @@ export function ObjectPreviewModal({ show, onShowChange, object }: ObjectPreview
193185 }
194186 } , [ show , previewMode , previewUrl , t ] )
195187
188+ React . useEffect ( ( ) => {
189+ setAudioLoadError ( false )
190+ } , [ show , previewUrl ] )
191+
196192 React . useEffect ( ( ) => {
197193 const cachedSize = previewUrl ? imageSizeCacheRef . current [ previewUrl ] : undefined
198194 setImageNaturalSize ( cachedSize ?? null )
@@ -375,6 +371,20 @@ export function ObjectPreviewModal({ show, onShowChange, object }: ObjectPreview
375371 </ div >
376372 </ div >
377373 )
374+ case "audio" :
375+ return audioLoadError ? (
376+ < div className = "my-auto text-center text-sm text-destructive" role = "alert" >
377+ { t ( "Preview unavailable" ) }
378+ </ div >
379+ ) : (
380+ < audio
381+ controls
382+ src = { previewUrl }
383+ className = "my-auto w-full"
384+ aria-label = { objectKey || t ( "Preview" ) }
385+ onError = { ( ) => setAudioLoadError ( true ) }
386+ />
387+ )
378388 case "sandbox" :
379389 return (
380390 < iframe src = { previewUrl } className = "h-[70vh] w-full" frameBorder = { 0 } title = "Sandbox preview" sandbox = "" />
0 commit comments