import React, { useState, useEffect, useRef } from 'react' import Editor from '@monaco-editor/react' import { ComponentDefinition } from '../../proxy/developerKit/componentInfo' import { generateSingleComponentJSX, generateUniqueId } from '@/utils/codeParser' import { FaCheck, FaCode, FaSpinner, FaMousePointer, FaSave, FaCog, FaTimes } from 'react-icons/fa' interface CodeEditorProps { code: string onChange: (code: string) => void onApplyCodeChanges: (code: string) => void onResetCodeChanges: () => void language?: string theme?: 'vs-dark' | 'light' parseError?: string | null onCursorChange?: (componentId: string | null) => void onDrop?: (componentDef: ComponentDefinition, position: { line: number; column: number }) => void onComponentAdded?: (componentDef: ComponentDefinition) => void onComponentSave: () => void } export const CodeEditor: React.FC = ({ code, onChange, onApplyCodeChanges, language = 'typescript', theme = 'vs-dark', onCursorChange, onComponentAdded, onComponentSave, }) => { const [localCode, setLocalCode] = useState(code) const [hasChanges, setHasChanges] = useState(false) const [isFormatting, setIsFormatting] = useState(false) const [showSettings, setShowSettings] = useState(false) const [editorTheme, setEditorTheme] = useState(theme) const [fontSize, setFontSize] = useState(14) const [wordWrap, setWordWrap] = useState<'on' | 'off'>('on') const [minimap, setMinimap] = useState(true) const [showSuccessMessage, setShowSuccessMessage] = useState(false) const [isDragOver, setIsDragOver] = useState(false) const [dropIndicator, setDropIndicator] = useState<{ show: boolean line: number column: number }>({ show: false, line: 0, column: 0 }) const editorRef = useRef(null) const containerRef = useRef(null) const cursorChangeTimeout = useRef(null) useEffect(() => { setLocalCode(code) setHasChanges(false) }, [code]) useEffect(() => { return () => { if (cursorChangeTimeout.current) { clearTimeout(cursorChangeTimeout.current) } } }, []) const findComponentIdAtPosition = ( code: string, position: { lineNumber: number; column: number }, ): string | null => { const lines = code.split('\n') const currentLineIndex = position.lineNumber - 1 // Önce bulunduğu satırı kontrol et const line = lines[currentLineIndex] || '' const idMatch = line.match(/id=["']([\w-]+)["']/) if (idMatch) return idMatch[1] // Eğer bulunduğu satırda yoksa, yakındaki satırları kontrol et // Önce yukarı doğru ara, sonra aşağı doğru ara for (let distance = 1; distance <= 3; distance++) { // Yukarı ara if (currentLineIndex - distance >= 0) { const upLine = lines[currentLineIndex - distance] || '' // Component tag başlangıcını kontrol et const componentMatch = upLine.match(/<([A-Z][a-zA-Z]*)/) if (componentMatch) { // Bu component tag'inin ID'sini ara (aynı satırda veya sonraki satırlarda) for ( let j = currentLineIndex - distance; j <= Math.min(lines.length - 1, currentLineIndex + 2); j++ ) { const searchLine = lines[j] || '' const foundId = searchLine.match(/id=["']([\w-]+)["']/) if (foundId) return foundId[1] } } } // Aşağı ara if (currentLineIndex + distance < lines.length) { const downLine = lines[currentLineIndex + distance] || '' const componentMatch = downLine.match(/<([A-Z][a-zA-Z]*)/) if (componentMatch) { // Bu component tag'inin ID'sini ara for ( let j = currentLineIndex + distance; j <= Math.min(lines.length - 1, currentLineIndex + distance + 2); j++ ) { const searchLine = lines[j] || '' const foundId = searchLine.match(/id=["']([\w-]+)["']/) if (foundId) return foundId[1] } } } } // Hiçbir şey bulunamadıysa, cursor'ın bulunduğu satırda herhangi bir JSX tag var mı kontrol et const jsxMatch = line.match(/<\/?([A-Z][a-zA-Z]*)/) if (jsxMatch) { // Bu tag'e ait ID'yi bulabilir miyiz? const componentName = jsxMatch[1] for ( let i = Math.max(0, currentLineIndex - 5); i <= Math.min(lines.length - 1, currentLineIndex + 5); i++ ) { const searchLine = lines[i] || '' if (searchLine.includes(`<${componentName}`) && searchLine.includes('id=')) { const foundId = searchLine.match(/id=["']([\w-]+)["']/) if (foundId) return foundId[1] } } } return null } const handleEditorCursorChange = (_event: any) => { if (!editorRef.current) return const position = editorRef.current.getPosition() if (!position) return // Throttle the cursor change to avoid too many calls if (cursorChangeTimeout.current) { clearTimeout(cursorChangeTimeout.current) } const id = findComponentIdAtPosition(localCode, position) if (onCursorChange) onCursorChange(id) } const handleEditorDidMount = (editor: any, monaco: any) => { editorRef.current = editor monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ target: monaco.languages.typescript.ScriptTarget.Latest, allowNonTsExtensions: true, moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs, module: monaco.languages.typescript.ModuleKind.CommonJS, noEmit: true, esModuleInterop: true, jsx: monaco.languages.typescript.JsxEmit.React, reactNamespace: 'React', allowJs: true, typeRoots: ['node_modules/@types'], }) editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { handleApplyChanges() }) editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, () => { handleFormatCode() }) editor.onDidChangeCursorPosition(handleEditorCursorChange) } const handleCodeChange = (value: string | undefined) => { if (value !== undefined) { setLocalCode(value) setHasChanges(value !== code) onChange(value) } } const handleApplyChanges = () => { onApplyCodeChanges(localCode) setHasChanges(false) setShowSuccessMessage(true) handleFormatCode() setTimeout(() => setShowSuccessMessage(false), 3000) } const handleFormatCode = async () => { if (editorRef.current) { setIsFormatting(true) try { await editorRef.current.getAction('editor.action.formatDocument').run() } catch (error) { console.error('Kod formatlama hatası:', error) } finally { setIsFormatting(false) } } } const handleResetChanges = () => { setLocalCode(code) setHasChanges(false) } // Drag & Drop handlers for Code Editor const handleDragOver = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() if (!editorRef.current || !containerRef.current) return try { const hasComponentData = e.dataTransfer.types.some( (type) => type === 'application/json' || type === 'text/plain' || type === 'text', ) if (hasComponentData) { e.dataTransfer.dropEffect = 'copy' setIsDragOver(true) // Get cursor position from mouse coordinates const rect = containerRef.current.getBoundingClientRect() const x = e.clientX - rect.left const y = e.clientY - rect.top // Convert to editor position const position = editorRef.current.getTargetAtClientPoint(x, y) if (position) { setDropIndicator({ show: true, line: position.position.lineNumber, column: position.position.column, }) } } } catch (err) { console.warn('Error handling drag over:', err) } } const handleDragLeave = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() if (!containerRef.current) return const rect = containerRef.current.getBoundingClientRect() const x = e.clientX const y = e.clientY if (x <= rect.left || x >= rect.right || y <= rect.top || y >= rect.bottom) { setIsDragOver(false) setDropIndicator({ show: false, line: 0, column: 0 }) } } const handleDrop = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() setIsDragOver(false) setDropIndicator({ show: false, line: 0, column: 0 }) if (!editorRef.current || !containerRef.current) return try { let componentDefData const formats = ['application/json', 'text/plain', 'text'] // Sürüklenen bileşenin verisini al for (const format of formats) { const data = e.dataTransfer.getData(format) if (data) { componentDefData = data break } } if (!componentDefData) return let componentDef try { componentDef = JSON.parse(componentDefData) } catch (error) { console.error('Component verisi çözümlenemedi:', error) return } // 1. Component ID'sini oluştur const componentId = generateUniqueId() // 2. Props'ları adapt et ve ID'yi dahil et const adaptedProps = { ...(componentDef.properties || []).reduce( (acc: any, prop: any) => { acc[prop.name] = { type: prop.type, value: prop.value, ...(prop.options ? { options: prop.options } : {}), } return acc }, {} as Record, ), id: { type: 'string', value: componentId, }, } // 3. JSX oluştur const componentJSX = generateSingleComponentJSX(componentDef.name, adaptedProps) // 4. Component tanımını ID ile genişlet const componentDefWithId = { ...componentDef, id: componentId, properties: [ ...(componentDef.properties || []).filter((p: any) => p.name !== 'id'), { name: 'id', type: 'string', value: componentId, category: 'properties', }, ], } // 5. Pozisyonu al let position = editorRef.current.getPosition() if (!position) { console.error('Geçersiz pozisyon tespit edildi. Yedek pozisyon kullanılıyor.') position = { lineNumber: 0, column: 0 } } // 6. JSX kodunu pozisyonda ekle const newCode = insertJSXAtPosition(localCode, componentJSX, position) setLocalCode(newCode) onChange(newCode) // 7. Parent'a bildirim gönder if (onComponentAdded) { onComponentAdded(componentDefWithId) } // 8. Uygulamayı tetikle setTimeout(() => { onApplyCodeChanges(newCode) }, 100) } catch (error) { console.error('Bileşen bırakılırken hata oluştu:', error) } } const insertJSXAtPosition = ( code: string, jsx: string, position: { lineNumber: number; column: number }, ): string => { const updatedCode = code const lines = updatedCode.split('\n') // Pozisyonu doğrula (import eklendiyse satır numaraları değişmiş olabilir) if (position.lineNumber < 1 || position.lineNumber > lines.length) { console.error('Geçersiz satır numarası:', position.lineNumber) position.lineNumber = lines.length // Geçersiz satırda son satıra ekle } const line = lines[position.lineNumber - 1] // 1 tabanlı indeks, 0 tabanlıya çevrilir if (!line) { console.error('Satır bulunamadı:', position.lineNumber) return updatedCode // Eğer satır yoksa, kodu değiştirmeden döndür } // Satırın indentasyonunu belirle const indentMatch = line.match(/^(\s*)/) const indent = indentMatch ? indentMatch[1] : ' ' // Default 6 boşluk // Component'i yeni satıra ekle (mevcut satırın altına) const formattedJSX = `${indent}${jsx}` lines.splice(position.lineNumber, 0, formattedJSX) return lines.join('\n') // Tüm satırları birleştir ve yeni kodu döndür } const handleDragEnter = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() } return (
{/* Drop Indicator */} {dropIndicator.show && (
Bileşeni Line {dropIndicator.line}, Column {dropIndicator.column} konumuna bırak
)} {/* Header */}
{/* Settings Panel */} {showSettings && (
setFontSize(parseInt(e.target.value))} className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
)} {/* Editor */}
{/* Status Bar */}
TypeScript React UTF-8 LF
Satır 1, Sütun 1 Boşluklar: 2 {hasChanges && ● Kaydedilmemiş değişiklikler} {showSuccessMessage && ( )} {isDragOver && ( Bileşeni bırakmaya hazır )}
) }