Spaces:
Running
Running
"use client"; | |
import React, { useState, useEffect } from 'react'; | |
import { FileType } from '@/types'; | |
import { FileTypeDetector } from '@/lib/file-type-detector'; | |
import { | |
Dialog, | |
DialogContent, | |
DialogDescription, | |
DialogFooter, | |
DialogHeader, | |
DialogTitle, | |
} from '@/components/ui/dialog'; | |
import { | |
Select, | |
SelectContent, | |
SelectItem, | |
SelectTrigger, | |
SelectValue, | |
} from '@/components/ui/select'; | |
import { Button } from '@/components/ui/button'; | |
import { Input } from '@/components/ui/input'; | |
import { Label } from '@/components/ui/label'; | |
import { Textarea } from '@/components/ui/textarea'; | |
import { Badge } from '@/components/ui/badge'; | |
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; | |
import { Checkbox } from '@/components/ui/checkbox'; | |
import { | |
FileText, | |
// Code, | |
// Image, | |
// Settings, | |
Sparkles, | |
Copy | |
} from 'lucide-react'; | |
export interface CreateFileDialogProps { | |
open: boolean; | |
onOpenChange: (open: boolean) => void; | |
onFileCreate: (fileData: { | |
name: string; | |
content: string; | |
type: FileType; | |
parentFolderId?: string; | |
}) => void; | |
parentFolderId?: string | null; | |
} | |
const FILE_TEMPLATES: Record<FileType, string> = { | |
html: `<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Document</title> | |
</head> | |
<body> | |
<h1>Hello World</h1> | |
</body> | |
</html>`, | |
css: `/* CSS Styles */ | |
body { | |
font-family: Arial, sans-serif; | |
margin: 0; | |
padding: 20px; | |
background-color: #f5f5f5; | |
} | |
.container { | |
max-width: 1200px; | |
margin: 0 auto; | |
background: white; | |
padding: 20px; | |
border-radius: 8px; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
}`, | |
js: `// JavaScript | |
console.log('Hello World'); | |
function greet(name) { | |
return \`Hello, \${name}!\`; | |
} | |
// Example usage | |
const message = greet('World'); | |
console.log(message);`, | |
ts: `// TypeScript | |
interface User { | |
id: number; | |
name: string; | |
email: string; | |
} | |
function greetUser(user: User): string { | |
return \`Hello, \${user.name}!\`; | |
} | |
// Example usage | |
const user: User = { | |
id: 1, | |
name: 'John Doe', | |
email: 'john@example.com' | |
}; | |
console.log(greetUser(user));`, | |
jsx: `import React from 'react'; | |
function Component() { | |
return ( | |
<div> | |
<h1>Hello World</h1> | |
<p>This is a React component.</p> | |
</div> | |
); | |
} | |
export default Component;`, | |
tsx: `import React from 'react'; | |
interface Props { | |
title: string; | |
children?: React.ReactNode; | |
} | |
const Component: React.FC<Props> = ({ title, children }) => { | |
return ( | |
<div> | |
<h1>{title}</h1> | |
{children} | |
</div> | |
); | |
}; | |
export default Component;`, | |
vue: `<template> | |
<div> | |
<h1>{{ title }}</h1> | |
<p>{{ message }}</p> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: 'Component', | |
data() { | |
return { | |
title: 'Hello World', | |
message: 'This is a Vue component.' | |
} | |
} | |
} | |
</script> | |
<style scoped> | |
h1 { | |
color: #42b883; | |
} | |
</style>`, | |
json: `{ | |
"name": "example", | |
"version": "1.0.0", | |
"description": "Example JSON file", | |
"main": "index.js", | |
"scripts": { | |
"start": "node index.js" | |
}, | |
"keywords": [], | |
"author": "", | |
"license": "MIT" | |
}`, | |
md: `# Document Title | |
## Introduction | |
This is a markdown document. | |
## Features | |
- **Bold text** | |
- *Italic text* | |
- [Links](https://example.com) | |
- \`Code snippets\` | |
## Code Example | |
\`\`\`javascript | |
console.log('Hello World'); | |
\`\`\` | |
## Conclusion | |
Thank you for reading!`, | |
txt: '', | |
py: `#!/usr/bin/env python3 | |
""" | |
Python script example | |
""" | |
def greet(name: str) -> str: | |
"""Greet a person by name.""" | |
return f"Hello, {name}!" | |
def main(): | |
"""Main function.""" | |
message = greet("World") | |
print(message) | |
if __name__ == "__main__": | |
main()`, | |
php: `<?php | |
/** | |
* PHP script example | |
*/ | |
function greet($name) { | |
return "Hello, " . $name . "!"; | |
} | |
// Example usage | |
$message = greet("World"); | |
echo $message; | |
?>`, | |
xml: `<?xml version="1.0" encoding="UTF-8"?> | |
<root> | |
<item id="1"> | |
<name>Example</name> | |
<description>This is an example XML file</description> | |
</item> | |
</root>`, | |
svg: `<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"> | |
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" /> | |
</svg>`, | |
yaml: `# YAML configuration file | |
name: example | |
version: 1.0.0 | |
description: Example YAML file | |
database: | |
host: localhost | |
port: 5432 | |
name: mydb | |
features: | |
- authentication | |
- logging | |
- caching`, | |
yml: `# YAML configuration file | |
name: example | |
version: 1.0.0 | |
description: Example YAML file | |
database: | |
host: localhost | |
port: 5432 | |
name: mydb | |
features: | |
- authentication | |
- logging | |
- caching`, | |
png: '', | |
jpg: '', | |
jpeg: '', | |
gif: '', | |
ico: '', | |
webp: '', | |
pdf: '' | |
}; | |
export function CreateFileDialog({ | |
open, | |
onOpenChange, | |
onFileCreate, | |
parentFolderId | |
}: CreateFileDialogProps) { | |
const [fileName, setFileName] = useState(''); | |
const [fileType, setFileType] = useState<FileType>('html'); | |
const [content, setContent] = useState(''); | |
const [useTemplate, setUseTemplate] = useState(true); | |
const [isCreating, setIsCreating] = useState(false); | |
// Reset form when dialog opens | |
useEffect(() => { | |
if (open) { | |
setFileName(''); | |
setFileType('html'); | |
setContent(''); | |
setUseTemplate(true); | |
} | |
}, [open]); | |
// Update content when file type or template preference changes | |
useEffect(() => { | |
if (useTemplate && FILE_TEMPLATES[fileType]) { | |
setContent(FILE_TEMPLATES[fileType]); | |
} else if (!useTemplate) { | |
setContent(''); | |
} | |
}, [fileType, useTemplate]); | |
// Auto-detect file type from name | |
useEffect(() => { | |
if (fileName) { | |
const detectedType = FileTypeDetector.detectFileType('', fileName); | |
if (detectedType !== 'txt') { | |
setFileType(detectedType); | |
} | |
} | |
}, [fileName]); | |
const handleCreate = async () => { | |
if (!fileName.trim()) { | |
return; | |
} | |
setIsCreating(true); | |
try { | |
onFileCreate({ | |
name: fileName.trim(), | |
content, | |
type: fileType, | |
parentFolderId: parentFolderId || undefined | |
}); | |
onOpenChange(false); | |
} catch (error) { | |
console.error('Error creating file:', error); | |
} finally { | |
setIsCreating(false); | |
} | |
}; | |
const getFileTypeCategories = () => { | |
return FileTypeDetector.getTypesByCategory(); | |
}; | |
const getFileIcon = (type: FileType) => { | |
return FileTypeDetector.getFileIcon(type); | |
}; | |
const generateFileName = () => { | |
const extensions: Record<FileType, string> = { | |
html: '.html', css: '.css', js: '.js', ts: '.ts', | |
jsx: '.jsx', tsx: '.tsx', vue: '.vue', json: '.json', | |
md: '.md', txt: '.txt', py: '.py', php: '.php', | |
xml: '.xml', svg: '.svg', yaml: '.yaml', yml: '.yml', | |
png: '.png', jpg: '.jpg', jpeg: '.jpeg', gif: '.gif', | |
ico: '.ico', webp: '.webp', pdf: '.pdf' | |
}; | |
const baseName = fileType === 'html' ? 'index' : | |
fileType === 'css' ? 'style' : | |
fileType === 'js' ? 'script' : | |
fileType === 'md' ? 'README' : | |
'untitled'; | |
setFileName(baseName + extensions[fileType]); | |
}; | |
return ( | |
<Dialog open={open} onOpenChange={onOpenChange}> | |
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto"> | |
<DialogHeader> | |
<DialogTitle className="flex items-center gap-2"> | |
<FileText className="h-5 w-5" /> | |
Create New File | |
</DialogTitle> | |
<DialogDescription> | |
Create a new file in your project | |
{parentFolderId && " in the selected folder"}. | |
</DialogDescription> | |
</DialogHeader> | |
<Tabs defaultValue="basic" className="w-full"> | |
<TabsList className="grid w-full grid-cols-2"> | |
<TabsTrigger value="basic">Basic</TabsTrigger> | |
<TabsTrigger value="advanced">Advanced</TabsTrigger> | |
</TabsList> | |
<TabsContent value="basic" className="space-y-4"> | |
{/* File Name */} | |
<div className="space-y-2"> | |
<Label htmlFor="fileName">File Name</Label> | |
<div className="flex gap-2"> | |
<Input | |
id="fileName" | |
placeholder="Enter file name..." | |
value={fileName} | |
onChange={(e) => setFileName(e.target.value)} | |
className="flex-1" | |
/> | |
<Button | |
variant="outline" | |
size="sm" | |
onClick={generateFileName} | |
title="Generate file name" | |
> | |
<Sparkles className="h-4 w-4" /> | |
</Button> | |
</div> | |
</div> | |
{/* File Type */} | |
<div className="space-y-2"> | |
<Label htmlFor="fileType">File Type</Label> | |
<Select value={fileType} onValueChange={(value) => setFileType(value as FileType)}> | |
<SelectTrigger> | |
<SelectValue> | |
<div className="flex items-center gap-2"> | |
<span>{getFileIcon(fileType)}</span> | |
<span>{fileType.toUpperCase()}</span> | |
</div> | |
</SelectValue> | |
</SelectTrigger> | |
<SelectContent> | |
{Object.entries(getFileTypeCategories()).map(([category, types]) => ( | |
<div key={category}> | |
<div className="px-2 py-1 text-xs font-semibold text-neutral-500 uppercase"> | |
{category} | |
</div> | |
{types.map(type => ( | |
<SelectItem key={type} value={type}> | |
<div className="flex items-center gap-2"> | |
<span>{getFileIcon(type)}</span> | |
<span>{type.toUpperCase()}</span> | |
</div> | |
</SelectItem> | |
))} | |
</div> | |
))} | |
</SelectContent> | |
</Select> | |
</div> | |
{/* Template Option */} | |
<div className="flex items-center space-x-2"> | |
<Checkbox | |
id="useTemplate" | |
checked={useTemplate} | |
onCheckedChange={(checked) => setUseTemplate(checked as boolean)} | |
/> | |
<Label htmlFor="useTemplate">Use template</Label> | |
{useTemplate && ( | |
<Badge variant="secondary" className="text-xs"> | |
Template available | |
</Badge> | |
)} | |
</div> | |
</TabsContent> | |
<TabsContent value="advanced" className="space-y-4"> | |
{/* Content Editor */} | |
<div className="space-y-2"> | |
<Label htmlFor="content">Initial Content</Label> | |
<Textarea | |
id="content" | |
placeholder="Enter file content..." | |
value={content} | |
onChange={(e) => setContent(e.target.value)} | |
className="min-h-[200px] font-mono text-sm" | |
/> | |
<div className="flex justify-between text-xs text-neutral-500"> | |
<span>{content.length} characters</span> | |
<Button | |
variant="ghost" | |
size="sm" | |
onClick={() => navigator.clipboard.writeText(content)} | |
className="h-auto p-1" | |
> | |
<Copy className="h-3 w-3" /> | |
</Button> | |
</div> | |
</div> | |
</TabsContent> | |
</Tabs> | |
<DialogFooter> | |
<Button variant="outline" onClick={() => onOpenChange(false)}> | |
Cancel | |
</Button> | |
<Button | |
onClick={handleCreate} | |
disabled={!fileName.trim() || isCreating} | |
> | |
{isCreating ? 'Creating...' : 'Create File'} | |
</Button> | |
</DialogFooter> | |
</DialogContent> | |
</Dialog> | |
); | |
} | |