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