709 lines
25 KiB
TypeScript
709 lines
25 KiB
TypeScript
|
|
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<PanelState>({
|
|||
|
|
toolbox: true,
|
|||
|
|
properties: true,
|
|||
|
|
})
|
|||
|
|
const [editorState, setEditorState] = useState<EditorState>({
|
|||
|
|
code: INITIAL_CODE,
|
|||
|
|
components: [],
|
|||
|
|
selectedComponentId: null,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const isEditing = !!id
|
|||
|
|
const [code, setCode] = useState<string>(INITIAL_CODE)
|
|||
|
|
const [hasCodeChanges, setHasCodeChanges] = useState(false)
|
|||
|
|
const [isLoaded, setIsLoaded] = useState(false)
|
|||
|
|
const [name, setName] = useState('')
|
|||
|
|
const [dependencies, setDependencies] = useState<string[]>([])
|
|||
|
|
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(
|
|||
|
|
<Notification type="success" duration={2000}>
|
|||
|
|
"Bileşen başarıyla kaydedildi."
|
|||
|
|
</Notification>,
|
|||
|
|
{
|
|||
|
|
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<string, any> = {}
|
|||
|
|
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<HTMLDivElement>) => {
|
|||
|
|
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<string>()
|
|||
|
|
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<string, any>) => {
|
|||
|
|
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 <ComponentLibrary onDragStart={handleDragStart} />
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 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 (
|
|||
|
|
<div className="flex flex-col flex-1 min-h-0 h-full w-full">
|
|||
|
|
<ComponentSelector
|
|||
|
|
components={editorState.components}
|
|||
|
|
selectedComponentId={editorState.selectedComponentId}
|
|||
|
|
onSelectComponent={handleSelectComponent}
|
|||
|
|
onRefresh={handleRefreshComponents}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<PropertyPanel
|
|||
|
|
selectedComponent={selectedComponent}
|
|||
|
|
currentCode={editorState.code}
|
|||
|
|
onPropertiesChange={handlePropertiesChange}
|
|||
|
|
onHookToggle={handleHookToggle}
|
|||
|
|
onMultipleHookToggle={applyMultipleHookToggles}
|
|||
|
|
onDeleteComponent={handleDeleteComponent}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const mainContent = (
|
|||
|
|
<div className="flex-1 flex flex-col min-h-0 h-full">
|
|||
|
|
{/* Top Header */}
|
|||
|
|
<div className="bg-white border-b border-gray-200 px-3 py-3">
|
|||
|
|
<div className="flex items-center justify-between">
|
|||
|
|
<div className="flex items-center space-x-4">
|
|||
|
|
<h1 className="text-lg font-semibold text-gray-900">{name}</h1>
|
|||
|
|
<p className="text-xs text-gray-500">{dependencies.join(', ')}</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex items-center space-x-3">
|
|||
|
|
<button
|
|||
|
|
onClick={() => setShowPanelManager(true)}
|
|||
|
|
className="flex items-center space-x-2 px-3 py-2 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
|||
|
|
title="Panel Manager"
|
|||
|
|
>
|
|||
|
|
<FaThLarge className="w-4 h-4 text-gray-600" />
|
|||
|
|
<span className="text-sm text-gray-600">Panels</span>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex flex-col flex-1 min-h-0 h-full">
|
|||
|
|
<div className="flex-1 h-full min-h-0">
|
|||
|
|
<CodeEditor
|
|||
|
|
code={code}
|
|||
|
|
onChange={handleCodeChange}
|
|||
|
|
onApplyCodeChanges={handleApplyCodeChanges}
|
|||
|
|
onResetCodeChanges={handleResetCodeChanges}
|
|||
|
|
onDrop={handleDropToCodeEditor}
|
|||
|
|
onComponentSave={handleSave}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div
|
|||
|
|
className="flex h-screen bg-gray-50"
|
|||
|
|
onDragOver={handleAppDragOver}
|
|||
|
|
onDrop={(e) => {
|
|||
|
|
e.preventDefault()
|
|||
|
|
e.stopPropagation()
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
{/* Panel Yöneticisi Modal */}
|
|||
|
|
{showPanelManager && (
|
|||
|
|
<PanelManager
|
|||
|
|
isOpen={showPanelManager}
|
|||
|
|
onClose={() => setShowPanelManager(false)}
|
|||
|
|
panelState={panelState}
|
|||
|
|
onPanelToggle={(panel) => setPanelState((prev) => ({ ...prev, [panel]: !prev[panel] }))}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
{/* Sol Sidebar ve ana içerik */}
|
|||
|
|
{panelState.toolbox ? (
|
|||
|
|
<Splitter direction="horizontal" initialSize={288} minSize={200} maxSize={400}>
|
|||
|
|
{renderLeftPanel()}
|
|||
|
|
<div className="flex flex-1">
|
|||
|
|
{panelState.properties ? (
|
|||
|
|
<Splitter
|
|||
|
|
direction="horizontal"
|
|||
|
|
initialSize={400}
|
|||
|
|
minSize={300}
|
|||
|
|
maxSize={window.innerWidth - 288 - 100}
|
|||
|
|
reverse={true}
|
|||
|
|
>
|
|||
|
|
{mainContent}
|
|||
|
|
{renderRightPanel()}
|
|||
|
|
</Splitter>
|
|||
|
|
) : (
|
|||
|
|
mainContent
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</Splitter>
|
|||
|
|
) : (
|
|||
|
|
<div className="flex flex-1">
|
|||
|
|
{panelState.properties ? (
|
|||
|
|
<Splitter
|
|||
|
|
direction="horizontal"
|
|||
|
|
initialSize={400}
|
|||
|
|
minSize={300}
|
|||
|
|
maxSize={window.innerWidth - 100}
|
|||
|
|
reverse={true}
|
|||
|
|
>
|
|||
|
|
{mainContent}
|
|||
|
|
{renderRightPanel()}
|
|||
|
|
</Splitter>
|
|||
|
|
) : (
|
|||
|
|
mainContent
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default CodeLayout
|