omnidev / components /file-explorer /create-file-dialog.tsx
kalhdrawi's picture
ุฃูˆู„ ุฑูุน ู„ู„ู…ู„ูุงุช ุฅู„ู‰ ุงู„ุณุจูŠุณ kalhdrawi/omnidev
1cf8f01
raw
history blame
12.3 kB
"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>
);
}