feat: initialize Convert UI - browser-based file conversion app
- Add Next.js 16 with Turbopack and React 19 - Add Tailwind CSS 4 with OKLCH color system - Implement FFmpeg.wasm for video/audio conversion - Implement ImageMagick WASM for image conversion - Add file upload with drag-and-drop - Add format selector with fuzzy search - Add conversion preview and download - Add conversion history with localStorage - Add dark/light theme support - Support 22+ file formats across video, audio, and images 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
95
lib/utils/fileUtils.ts
Normal file
95
lib/utils/fileUtils.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Format file size in human-readable format
|
||||
*/
|
||||
export function formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return `${Math.round(bytes / Math.pow(k, i) * 100) / 100} ${sizes[i]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate file size (max 500MB for browser processing)
|
||||
*/
|
||||
export function validateFileSize(file: File, maxSizeMB: number = 500): boolean {
|
||||
const maxBytes = maxSizeMB * 1024 * 1024;
|
||||
return file.size <= maxBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file extension from filename
|
||||
*/
|
||||
export function getFileExtension(filename: string): string {
|
||||
const lastDot = filename.lastIndexOf('.');
|
||||
return lastDot === -1 ? '' : filename.substring(lastDot + 1).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filename without extension
|
||||
*/
|
||||
export function getFilenameWithoutExtension(filename: string): string {
|
||||
const lastDot = filename.lastIndexOf('.');
|
||||
return lastDot === -1 ? filename : filename.substring(0, lastDot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate output filename
|
||||
*/
|
||||
export function generateOutputFilename(inputFilename: string, outputExtension: string): string {
|
||||
const basename = getFilenameWithoutExtension(inputFilename);
|
||||
return `${basename}.${outputExtension}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download blob as file
|
||||
*/
|
||||
export function downloadBlob(blob: Blob, filename: string): void {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read file as ArrayBuffer
|
||||
*/
|
||||
export async function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve(reader.result as ArrayBuffer);
|
||||
reader.onerror = () => reject(reader.error);
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Read file as Data URL
|
||||
*/
|
||||
export async function readFileAsDataURL(file: File): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve(reader.result as string);
|
||||
reader.onerror = () => reject(reader.error);
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate file type against allowed MIME types
|
||||
*/
|
||||
export function validateFileType(file: File, allowedTypes: string[]): boolean {
|
||||
return allowedTypes.some((type) => {
|
||||
if (type.endsWith('/*')) {
|
||||
const category = type.split('/')[0];
|
||||
return file.type.startsWith(`${category}/`);
|
||||
}
|
||||
return file.type === type;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user