import { useState, useEffect, useCallback } from 'react' import { FaThLarge } from 'react-icons/fa' import { parseReactCode, updateComponentProp, updateComponentProps, generateHookCode, generateHookVariableName, removeHookFromCode, generateUniqueId, removeComponentAndHooksFromCode, generateComponentJSX, insertJSXAtPosition, } from '../../utils/codeParser' import { ComponentLibrary } from '../../components/codeLayout/ComponentLibrary' import { Splitter } from '../../components/codeLayout/Splitter' import { PanelManager } from '../../components/codeLayout/PanelManager' import { CodeEditor } from '../../components/codeLayout/CodeEditor' import { ComponentDefinition, ComponentInfo, EditorState } from '../../proxy/developerKit/componentInfo' import PropertyPanel from '../../components/codeLayout/PropertyPanel' import ComponentSelector from '../../components/codeLayout/ComponentSelector' import { useParams } from 'react-router-dom' import { useComponents } from '../../contexts/ComponentContext' import { toast } from '../../components/ui' import Notification from '../../components/ui/Notification/Notification' import { PanelState } from '../../components/codeLayout/data/componentDefinitions' const INITIAL_CODE = `const Component = () => { return ( <> ); }; export default Component ` function CodeLayout() { const { id } = useParams() const { getComponent, updateComponent } = useComponents() const [showPanelManager, setShowPanelManager] = useState(false) const [panelState, setPanelState] = useState({ toolbox: true, properties: true, }) const [editorState, setEditorState] = useState({ code: INITIAL_CODE, components: [], selectedComponentId: null, }) const isEditing = !!id const [code, setCode] = useState(INITIAL_CODE) const [hasCodeChanges, setHasCodeChanges] = useState(false) const [isLoaded, setIsLoaded] = useState(false) const [name, setName] = useState('') const [dependencies, setDependencies] = useState([]) const [isActive, setIsActive] = useState(true) const handleSave = async () => { try { const componentData = { name: name.trim(), dependencies: JSON.stringify(dependencies), // Serialize dependencies to JSON string code: code.trim(), isActive, } if (isEditing && id) { updateComponent(id, componentData) parseAndUpdateComponents(componentData.code) } } catch (error) { console.error('Error saving component:', error) alert('Failed to save component. Please try again.') } finally { setHasCodeChanges(false) setIsLoaded(true) toast.push( "Bileşen başarıyla kaydedildi." , { placement: 'top-end', }, ) } } // Load existing component data - sadece edit modunda useEffect(() => { if (isEditing && id && !isLoaded) { const component = getComponent(id) if (component) { setName(component.name) // setDescription(component.description || ""); // Parse dependencies from JSON string try { const deps = component.dependencies ? JSON.parse(component.dependencies) : [] setDependencies(Array.isArray(deps) ? deps : []) } catch { setDependencies([]) } setCode(component.code) // Mevcut kodu yükle // Parse components from the loaded code parseAndUpdateComponents(component.code) setIsActive(component.isActive) setIsLoaded(true) } } else if (!isEditing && !isLoaded) { // Yeni komponent için boş başla - TEMPLATE YOK setIsLoaded(true) } }, [id, isEditing, getComponent, isLoaded]) // NEW: Handle drop to Code Editor instead of Canvas const handleDropToCodeEditor = ( componentDef: ComponentDefinition, position: { line: number; column: number }, ): void => { if (!componentDef || typeof componentDef !== 'object') { console.error('Component definition is null or undefined') return } if (!componentDef.name) { console.error('Invalid component definition in handleDropToCodeEditor') return } const propertiesObj: Record = {} if (Array.isArray(componentDef.properties)) { componentDef.properties.forEach((prop) => { propertiesObj[prop.name] = prop }) } const newComponent: ComponentInfo = { id: generateUniqueId(), type: componentDef.name, // type alanı componentDef.name olmalı props: propertiesObj, name: componentDef.name, startLine: 0, endLine: 0, startColumn: 0, endColumn: 0, } // Generate JSX string for this component const componentJSX = generateComponentJSX(newComponent) // Insert the JSX at the cursor position in the code editor setCode(insertJSXAtPosition(code, componentJSX, position)) } const handleAppDragOver = (e: React.DragEvent) => { e.preventDefault() } const parseAndUpdateComponents = useCallback((code: string) => { try { const parsed = parseReactCode(code) setEditorState((prev) => ({ ...prev, code, components: parsed.components, })) } catch (error) { const msg = error instanceof Error ? error.message : 'Koddan komponentler parse edilemedi.' console.log('Parse error:', msg) setEditorState((prev) => ({ ...prev, code, // Only update the code, keep existing components })) } }, []) // Handle code changes from Monaco Editor const handleCodeChange = useCallback( (value: string | undefined) => { if (value !== undefined) { setCode(value) setHasCodeChanges(value !== editorState.code) } }, [editorState.code], ) // Apply code changes const handleApplyCodeChanges = useCallback(() => { parseAndUpdateComponents(code) setHasCodeChanges(false) }, [parseAndUpdateComponents, code]) // Reset code changes const handleResetCodeChanges = useCallback(() => { setCode(editorState.code) setHasCodeChanges(false) }, [editorState.code]) // Handle property changes from Property Panel // Handle hook toggle const handleHookToggle = useCallback( (componentId: string, hookType: string, enabled: boolean) => { console.log('🪝 App: handleHookToggle called:', { componentId, hookType, enabled, }) const selectedComponent = editorState.components?.find((c) => c.id === componentId) if (!selectedComponent) return // Use the most up-to-date code (pendingCode if available, otherwise editorState.code) let updatedCode = code || editorState.code console.log('🔍 App: Using code source:', code === editorState.code ? 'same' : 'pendingCode') if (enabled) { // Check if hook is already present - more specific check const varName = generateHookVariableName(hookType, componentId) console.log('🔍 App: Checking for existing hook variable:', varName) // Create a more specific regex to check for actual hook declarations const hookDeclarationRegex = new RegExp( `const\\s+(?:\\[.*?${varName}.*?\\]|${varName})\\s*=\\s*${hookType}\\s*\\(`, ) if (hookDeclarationRegex.test(updatedCode)) { console.log('⚠️ App: Hook already exists, skipping') return // Hook already exists } // Handle React imports - improved approach const reactImportRegex = /^import\s+\{([^}]*)\}\s+from\s+['"]react['"]\s*;?/m const reactMatch = reactImportRegex.exec(updatedCode) if (reactMatch) { // reactMatch[1] --> süslü parantez içi örn: "useState, useRef" let existingHooks = reactMatch[1] .split(',') .map((h) => h.trim()) .filter((h) => h) // Eğer hookType yoksa ekle if (!existingHooks.includes(hookType)) { existingHooks.push(hookType) } // Tekrarları kaldır (güvenlik için) existingHooks = [...new Set(existingHooks)] const newImport = `import { ${existingHooks.join(', ')} } from 'react';\n` updatedCode = updatedCode.replace(reactImportRegex, newImport) console.log('🔄 App: Updated existing React import:', newImport.trim()) } else { // React import satırı yoksa ekle const importLine = `import { ${hookType} } from 'react';\n\n` updatedCode = importLine + updatedCode console.log('🔄 App: Added new React import:', importLine.trim()) } // Add hook declaration const hookCode = generateHookCode(hookType, componentId, selectedComponent.type) console.log('🔍 App: Generated hook code:', hookCode) // Try multiple patterns for function declaration const functionPatterns = [ /function\s+Component\s*\([^)]*\)\s*\{/, // function Component() { /function\s+\w+\s*\([^)]*\)\s*\{/, // function AnyName() { /const\s+\w+\s*=\s*\([^)]*\)\s*=>\s*\{/, // const Component = () => { /export\s+default\s+function\s*\([^)]*\)\s*\{/, // export default function() { ] let match2 = null for (const pattern of functionPatterns) { match2 = updatedCode.match(pattern) if (match2) { console.log('🔍 App: Found function with pattern:', pattern) break } } console.log('🔍 App: Function match result:', match2) console.log('🔍 App: Updated code preview:', updatedCode.substring(0, 500)) if (match2 && typeof match2.index === 'number') { const insertPosition = match2.index + match2[0].length updatedCode = updatedCode.slice(0, insertPosition) + '\n ' + hookCode + '\n' + updatedCode.slice(insertPosition) console.log('✅ App: Hook code inserted successfully') } else { console.log('⚠️ App: Could not find function body to insert hook') // Fallback: insert after the first opening brace const firstBrace = updatedCode.indexOf('{') if (firstBrace !== -1) { updatedCode = updatedCode.slice(0, firstBrace + 1) + '\n ' + hookCode + '\n' + updatedCode.slice(firstBrace + 1) console.log('✅ App: Hook code inserted using fallback method') } } // Update component properties if needed if (hookType === 'useState') { const setterName = `set${varName.charAt(0).toUpperCase() + varName.slice(1)}` // Update component props based on type if (selectedComponent.type === 'input') { updatedCode = updateComponentProp(updatedCode, componentId, 'value', `{${varName}}`) updatedCode = updateComponentProp( updatedCode, componentId, 'onChange', `{(e) => ${setterName}(e.target.value)}`, ) } else if (selectedComponent.type === 'button') { updatedCode = updateComponentProp( updatedCode, componentId, 'onClick', `{() => ${setterName}(!${varName})}`, ) } else if (selectedComponent.type === 'checkbox') { updatedCode = updateComponentProp(updatedCode, componentId, 'checked', `{${varName}}`) updatedCode = updateComponentProp( updatedCode, componentId, 'onChange', `{(val) => ${setterName}(val)}`, ) } } else if (hookType === 'useRef') { updatedCode = updateComponentProp(updatedCode, componentId, 'ref', `{${varName}}`) } } else { // Remove hook updatedCode = removeHookFromCode(updatedCode, hookType, componentId) // Remove related props if (hookType === 'useState') { if (selectedComponent.type === 'input') { updatedCode = updateComponentProp(updatedCode, componentId, 'value', '') updatedCode = updateComponentProp(updatedCode, componentId, 'onChange', null) } else if (selectedComponent.type === 'button') { updatedCode = updateComponentProp(updatedCode, componentId, 'onClick', null) } else if (selectedComponent.type === 'checkbox') { updatedCode = updateComponentProp(updatedCode, componentId, 'checked', false) updatedCode = updateComponentProp(updatedCode, componentId, 'onChange', null) } } else if (hookType === 'useRef') { updatedCode = updateComponentProp(updatedCode, componentId, 'ref', null) } } setEditorState((prev) => { console.log('🔄 App: Final updatedCode before parsing:', updatedCode) const parsed = parseReactCode(updatedCode) console.log('🔄 App: Parsed components:', parsed.components?.length) const newState = { code: updatedCode, components: parsed.components, selectedComponentId: prev.selectedComponentId, // Preserve selection } console.log('🔄 App: New editor state code preview:', newState.code.substring(0, 300)) return newState }) // Also update pending code to match setCode(updatedCode) }, [editorState.code, editorState.components, code], ) // Zincirleme hook güncelleme fonksiyonu const applyMultipleHookToggles = ( toggles: { componentId: string; hookType: string; enabled: boolean }[], ) => { let updatedCode = code || editorState.code // 1. Sadece kaldırılması gereken hook'ları kaldır toggles .filter((t) => t.enabled === false) .forEach(({ hookType, componentId }) => { const selectedComponent = editorState.components?.find((c) => c.id === componentId) if (!selectedComponent) return updatedCode = removeHookFromCode(updatedCode, hookType, componentId) // Prop temizliği if (hookType === 'useState') { if (selectedComponent.type === 'input') { updatedCode = updateComponentProp(updatedCode, componentId, 'value', '') updatedCode = updateComponentProp(updatedCode, componentId, 'onChange', null) } else if (selectedComponent.type === 'button') { updatedCode = updateComponentProp(updatedCode, componentId, 'onClick', null) } else if (selectedComponent.type === 'checkbox') { updatedCode = updateComponentProp(updatedCode, componentId, 'checked', false) updatedCode = updateComponentProp(updatedCode, componentId, 'onChange', null) } } else if (hookType === 'useRef') { updatedCode = updateComponentProp(updatedCode, componentId, 'ref', null) } }) // 2. Eklenmesi gereken hook'ları ekle (veya zaten varsa dokunma) toggles .filter((t) => t.enabled === true) .forEach(({ hookType, componentId }) => { const selectedComponent = editorState.components?.find((c) => c.id === componentId) if (!selectedComponent) return const varName = generateHookVariableName(hookType, componentId) // Hook kodu fonksiyon gövdesinde yoksa ekle const hookDeclarationRegex = new RegExp( `const\\s+(?:\\[.*?${varName}.*?\\]|${varName})\\s*=\\s*${hookType}\\s*\\(`, ) if (!hookDeclarationRegex.test(updatedCode)) { const reactImportRegex = // eslint-disable-next-line no-useless-escape /import\\s+React(?:\\s*,\\s*\\{([^}]*)\\})?\\s+from\\s+['\"]react['\"];?/ const importMatch = updatedCode.match(reactImportRegex) const allHooks = new Set() if (importMatch && importMatch[1]) { importMatch[1] .split(',') .map((h) => h.trim()) .filter((h) => h) .forEach((h) => allHooks.add(h)) } allHooks.add(hookType) let newImport = '' if (allHooks.size > 0) { newImport = `import { ${Array.from(allHooks).join(', ')} } from 'react';\n` } if (importMatch) { updatedCode = updatedCode.replace(reactImportRegex, newImport) } else { updatedCode = newImport + updatedCode } // Hook kodunu fonksiyon gövdesine ekle const hookCode = generateHookCode(hookType, componentId, selectedComponent.type) const functionPatterns = [ /function\s+Component\s*\([^)]*\)\s*\{/, // function Component() { /function\s+\w+\s*\([^)]*\)\s*\{/, // function AnyName() { /const\s+\w+\s*=\s*\([^)]*\)\s*=>\s*\{/, // const Component = () => { /export\s+default\s+function\s*\([^)]*\)\s*\{/, // export default function() { ] let match2 = null for (const pattern of functionPatterns) { match2 = updatedCode.match(pattern) if (match2) break } if (match2 && typeof match2.index === 'number') { const insertPosition = match2.index + match2[0].length updatedCode = updatedCode.slice(0, insertPosition) + '\n ' + hookCode + '\n' + updatedCode.slice(insertPosition) } else { const firstBrace = updatedCode.indexOf('{') if (firstBrace !== -1) { updatedCode = updatedCode.slice(0, firstBrace + 1) + '\n ' + hookCode + '\n' + updatedCode.slice(firstBrace + 1) } } } // Prop güncellemeleri if (hookType === 'useState') { const setterName = `set${varName.charAt(0).toUpperCase() + varName.slice(1)}` if (selectedComponent.type === 'input') { updatedCode = updateComponentProp(updatedCode, componentId, 'value', `{${varName}}`) updatedCode = updateComponentProp( updatedCode, componentId, 'onChange', `{(e) => ${setterName}(e.target.value)}`, ) } else if (selectedComponent.type === 'button') { updatedCode = updateComponentProp( updatedCode, componentId, 'onClick', `{() => ${setterName}(!${varName})}`, ) } else if (selectedComponent.type === 'checkbox') { updatedCode = updateComponentProp(updatedCode, componentId, 'checked', `{${varName}}`) updatedCode = updateComponentProp( updatedCode, componentId, 'onChange', `{(e) => ${setterName}(e.target.checked)}`, ) } } else if (hookType === 'useRef') { updatedCode = updateComponentProp(updatedCode, componentId, 'ref', `{${varName}}`) } }) setEditorState((prev) => { const parsed = parseReactCode(updatedCode) return { code: updatedCode, components: parsed.components, selectedComponentId: prev.selectedComponentId, } }) setCode(updatedCode) } // Handle multiple property changes at once const handlePropertiesChange = useCallback( (componentId: string, updates: Record) => { console.log('🔄 App: handlePropertiesChange called:', { componentId, updates, }) const updatedCode = updateComponentProps(editorState.code, componentId, updates) console.log('📝 App: Properties updated, code changed:', editorState.code !== updatedCode) setEditorState((prev) => { const parsed = parseReactCode(updatedCode) return { code: updatedCode, components: parsed.components, selectedComponentId: prev.selectedComponentId, // Preserve selection } }) // Update pending code to reflect changes setCode(updatedCode) }, [editorState.code], ) // Handle component list refresh const handleRefreshComponents = useCallback(() => { parseAndUpdateComponents(editorState.code) }, [parseAndUpdateComponents, editorState.code]) // Handle component selection const handleSelectComponent = useCallback((componentId: string | null) => { setEditorState((prev) => ({ ...prev, selectedComponentId: componentId, })) }, []) // Initialize parsing on mount useEffect(() => { parseAndUpdateComponents(INITIAL_CODE) }, [parseAndUpdateComponents]) const handleDragStart = (_componentDef: ComponentDefinition, e: React.DragEvent) => { e.stopPropagation() } const selectedComponent = editorState.components?.find((c) => c.id === editorState.selectedComponentId) || null const renderLeftPanel = () => { if (!panelState.toolbox) return null return } // Komponent ve ilgili hook'ları koddan silen fonksiyon const handleDeleteComponent = (componentId: string) => { // Koddan JSX ve hook'ları sil const updatedCode = removeComponentAndHooksFromCode(code, componentId) // Koddan parse edip state'i güncelle parseAndUpdateComponents(updatedCode) setCode(updatedCode) // Seçili komponenti kaldır setEditorState((prev) => ({ ...prev, selectedComponentId: null, })) } const renderRightPanel = () => { if (!panelState.properties) return null return (
) } const mainContent = (
{/* Top Header */}

{name}

{dependencies.join(', ')}

) return (
{ e.preventDefault() e.stopPropagation() }} > {/* Panel Yöneticisi Modal */} {showPanelManager && ( setShowPanelManager(false)} panelState={panelState} onPanelToggle={(panel) => setPanelState((prev) => ({ ...prev, [panel]: !prev[panel] }))} /> )} {/* Sol Sidebar ve ana içerik */} {panelState.toolbox ? ( {renderLeftPanel()}
{panelState.properties ? ( {mainContent} {renderRightPanel()} ) : ( mainContent )}
) : (
{panelState.properties ? ( {mainContent} {renderRightPanel()} ) : ( mainContent )}
)}
) } export default CodeLayout