feat: enhance file handling and preview features
- Added new dependencies for file type handling and markdown support in package.json. - Updated FileGrid component to use file extension for FilePreview. - Refactored FilePreview component to determine file type based on extension, supporting various media types including images, audio, video, text, archives, and code files. - Created a utility function getFileType to centralize MIME type determination based on file extensions. - Updated API route to utilize the new getFileType function for serving files with correct MIME types. - Added Tailwind CSS configuration with typography plugin for improved styling.
This commit is contained in:
		
							parent
							
								
									af978c98b8
								
							
						
					
					
						commit
						ad24c76a70
					
				
							
								
								
									
										3030
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3030
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -24,15 +24,21 @@ | |||||||
|     "@auth/prisma-adapter": "^2.7.2", |     "@auth/prisma-adapter": "^2.7.2", | ||||||
|     "@prisma/client": "^6.5.0", |     "@prisma/client": "^6.5.0", | ||||||
|     "@t3-oss/env-nextjs": "^0.12.0", |     "@t3-oss/env-nextjs": "^0.12.0", | ||||||
|  |     "@tailwindcss/typography": "^0.5.16", | ||||||
|     "@tanstack/react-query": "^5.69.0", |     "@tanstack/react-query": "^5.69.0", | ||||||
|     "@trpc/client": "^11.0.0", |     "@trpc/client": "^11.0.0", | ||||||
|     "@trpc/react-query": "^11.0.0", |     "@trpc/react-query": "^11.0.0", | ||||||
|     "@trpc/server": "^11.0.0", |     "@trpc/server": "^11.0.0", | ||||||
|  |     "dompurify": "^3.2.5", | ||||||
|  |     "mermaid": "^11.6.0", | ||||||
|     "next": "^15.2.3", |     "next": "^15.2.3", | ||||||
|     "next-auth": "5.0.0-beta.25", |     "next-auth": "5.0.0-beta.25", | ||||||
|     "react": "^19.0.0", |     "react": "^19.0.0", | ||||||
|     "react-dom": "^19.0.0", |     "react-dom": "^19.0.0", | ||||||
|     "react-hot-toast": "^2.5.2", |     "react-hot-toast": "^2.5.2", | ||||||
|  |     "react-markdown": "^10.1.0", | ||||||
|  |     "rehype-raw": "^7.0.0", | ||||||
|  |     "remark-gfm": "^4.0.1", | ||||||
|     "server-only": "^0.0.1", |     "server-only": "^0.0.1", | ||||||
|     "superjson": "^2.2.1", |     "superjson": "^2.2.1", | ||||||
|     "zod": "^3.24.2" |     "zod": "^3.24.2" | ||||||
|  | |||||||
| @ -103,7 +103,7 @@ export default function FileGrid({ session }: FileGridProps) { | |||||||
|             key={file.id} |             key={file.id} | ||||||
|             className="flex place-content-end max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20" |             className="flex place-content-end max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20" | ||||||
|           > |           > | ||||||
|             {fileType !== "unknown" && <div className=" self-center max-w-50"><FilePreview fileId={file.id} fileType={fileType} /></div>} |             {<div className=" self-center max-w-50"><FilePreview fileId={file.id} fileType={file.extension} /></div>} | ||||||
| 
 | 
 | ||||||
|             <button onClick={() => router.push(pageUrl + file.url)}> |             <button onClick={() => router.push(pageUrl + file.url)}> | ||||||
|               <h3 className="text-2xl font-bold">{file.name}</h3> |               <h3 className="text-2xl font-bold">{file.name}</h3> | ||||||
|  | |||||||
| @ -51,8 +51,37 @@ export function FilePreview({ fileId, fileType }: FilePreviewProps) { | |||||||
|   if (!mediaSrc) { |   if (!mediaSrc) { | ||||||
|     return <div>Loading...</div>; |     return <div>Loading...</div>; | ||||||
|   } |   } | ||||||
|  |   const getFileType = (extension: string): string => { | ||||||
|  |     const fileTypes: Record<string, string> = { | ||||||
|  |       ".mp4": "video/mp4", | ||||||
|  |       ".webm": "video/webm", | ||||||
|  |       ".ogg": "video/ogg", | ||||||
|  |       ".jpg": "image/jpeg", | ||||||
|  |       ".jpeg": "image/jpeg", | ||||||
|  |       ".png": "image/png", | ||||||
|  |       ".gif": "image/gif", | ||||||
|  |       ".svg": "image/svg+xml", | ||||||
|  |       ".mp3": "audio/mpeg", | ||||||
|  |       ".wav": "audio/wav", | ||||||
|  |       ".zip": "archive/zip", | ||||||
|  |       ".rar": "archive/rar", | ||||||
|  |       ".pdf": "text/pdf", | ||||||
|  |       ".txt": "text/plain", | ||||||
|  |       ".c" : "code/c", | ||||||
|  |       ".cpp" : "code/cpp", | ||||||
|  |       ".py" : "code/python", | ||||||
|  |       ".js" : "code/javascript", | ||||||
|  |       ".html" : "code/html", | ||||||
|  |       ".css" : "code/css", | ||||||
|  |       ".md" : "markdown/markdown", | ||||||
|  |       ".json" : "code/json", | ||||||
|  |       ".xml" : "code/xml", | ||||||
|  |       ".csv" : "code/csv", | ||||||
|  |     }; | ||||||
|  |     return fileTypes[extension] || "unknown"; | ||||||
|  |   }; | ||||||
| 
 | 
 | ||||||
|   if (fileType.startsWith("video")) { |   if (getFileType(fileType).startsWith("video")) { | ||||||
|     return ( |     return ( | ||||||
|       <video |       <video | ||||||
|         controls |         controls | ||||||
| @ -63,7 +92,7 @@ export function FilePreview({ fileId, fileType }: FilePreviewProps) { | |||||||
|       </video> |       </video> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   if (fileType.startsWith("audio")) { |   if (getFileType(fileType).startsWith("audio")) { | ||||||
|     return ( |     return ( | ||||||
|       <audio |       <audio | ||||||
|         controls |         controls | ||||||
| @ -74,6 +103,29 @@ export function FilePreview({ fileId, fileType }: FilePreviewProps) { | |||||||
|       </audio> |       </audio> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 |   if (getFileType(fileType).startsWith("image")) { | ||||||
|     return <img src={mediaSrc} alt="Media preview" className="max-w-full max-h-96 rounded-lg shadow-md" />; |     return <img src={mediaSrc} alt="Media preview" className="max-w-full max-h-96 rounded-lg shadow-md" />; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (getFileType(fileType).startsWith("text")) { | ||||||
|  |     return ( | ||||||
|  |       <img src="/icons/files/text.svg" alt="Text file preview" className="max-w-full max-h-96 rounded-lg invert" /> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |   if (getFileType(fileType).startsWith("archive")) { | ||||||
|  |     return ( | ||||||
|  |       <img src="/icons/files/archive.svg" alt="Archive file preview" className="max-w-full max-h-96 rounded-lg invert" /> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |   if (getFileType(fileType).startsWith("code")) { | ||||||
|  |     return ( | ||||||
|  |       <img src="/icons/files/code.svg" alt="Code file preview" className="max-w-full max-h-96 rounded-lg invert" /> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |   // if (getFileType(fileType).startsWith("markdown")) {
 | ||||||
|  |   //   return;      
 | ||||||
|  |   //   }
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   return; | ||||||
| } | } | ||||||
| @ -2,6 +2,7 @@ import { NextResponse } from "next/server"; | |||||||
| import path from "path"; | import path from "path"; | ||||||
| import { promises as fs } from "fs"; | import { promises as fs } from "fs"; | ||||||
| import { db } from "~/server/db"; | import { db } from "~/server/db"; | ||||||
|  | import { getFileType } from "~/utils/fileType"; | ||||||
| 
 | 
 | ||||||
| export async function GET(req: Request) { | export async function GET(req: Request) { | ||||||
|   const url = new URL(req.url); |   const url = new URL(req.url); | ||||||
| @ -27,25 +28,8 @@ export async function GET(req: Request) { | |||||||
|     // Read the file from the filesystem
 |     // Read the file from the filesystem
 | ||||||
|     const fileBuffer = await fs.readFile(filePath); |     const fileBuffer = await fs.readFile(filePath); | ||||||
| 
 | 
 | ||||||
|     const mimeType = file.extension === ".mp4" |     const mimeType = getFileType(path.extname(file.name)); // Get the MIME type based on the file extension
 | ||||||
|       ? "video/mp4" | 
 | ||||||
|       : file.extension === ".webm" |  | ||||||
|       ? "video/webm" |  | ||||||
|       : file.extension === ".ogg" |  | ||||||
|       ? "video/ogg" |  | ||||||
|       : file.extension === ".jpg" || file.extension === ".jpeg" |  | ||||||
|       ? "image/jpeg" |  | ||||||
|       : file.extension === ".png" |  | ||||||
|       ? "image/png" |  | ||||||
|       : file.extension === ".gif" |  | ||||||
|       ? "image/gif" |  | ||||||
|       : file.extension === ".svg" |  | ||||||
|       ? "image/svg+xml" |  | ||||||
|       : file.extension === ".mp3" |  | ||||||
|       ? "audio/mpeg" |  | ||||||
|       : file.extension === ".wav" |  | ||||||
|       ? "audio/wav" |  | ||||||
|       : "application/octet-stream"; |  | ||||||
| 
 | 
 | ||||||
|     // Return the file as a binary response
 |     // Return the file as a binary response
 | ||||||
|     return new Response(fileBuffer, { |     return new Response(fileBuffer, { | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								src/utils/fileType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/utils/fileType.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | // This function takes a file name as input and returns the file type based on its extension.
 | ||||||
|  | 
 | ||||||
|  | export function getFileType(extension: string): string { | ||||||
|  |     const fileTypes: Record<string, string> = { | ||||||
|  |         ".mp4": "video/mp4", | ||||||
|  |         ".webm": "video/webm", | ||||||
|  |         ".ogg": "video/ogg", | ||||||
|  |         ".jpg": "image/jpeg", | ||||||
|  |         ".jpeg": "image/jpeg", | ||||||
|  |         ".png": "image/png", | ||||||
|  |         ".gif": "image/gif", | ||||||
|  |         ".svg": "image/svg+xml", | ||||||
|  |         ".mp3": "audio/mpeg", | ||||||
|  |         ".wav": "audio/wav", | ||||||
|  |         ".zip": "archive/zip", | ||||||
|  |         ".rar": "archive/rar", | ||||||
|  |         ".pdf": "text/pdf", | ||||||
|  |         ".txt": "text/plain", | ||||||
|  |         ".c": "code/c", | ||||||
|  |         ".cpp": "code/cpp", | ||||||
|  |         ".py": "code/python", | ||||||
|  |         ".js": "code/javascript", | ||||||
|  |         ".html": "code/html", | ||||||
|  |         ".css": "code/css", | ||||||
|  |         ".md": "markdown/markdown", | ||||||
|  |         ".json": "code/json", | ||||||
|  |         ".xml": "code/xml", | ||||||
|  |         ".csv": "code/csv", | ||||||
|  |     }; | ||||||
|  |     return fileTypes[extension] || "unknown"; | ||||||
|  |   }; | ||||||
							
								
								
									
										6
									
								
								tailwind.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								tailwind.config.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | module.exports = { | ||||||
|  |   content: [ | ||||||
|  |     "./src/**/*.{js,ts,jsx,tsx}", // Ensure your paths are correct
 | ||||||
|  |   ], | ||||||
|  |   plugins: [require("@tailwindcss/typography")], | ||||||
|  | }; | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user