From 9fdfdab1e9de4bca86c50bfd2898d55357324a49 Mon Sep 17 00:00:00 2001 From: ZHallen122 Date: Fri, 4 Apr 2025 14:19:52 -0400 Subject: [PATCH 01/25] select component --- .../chat/code-engine/componentInspector.tsx | 647 ++++++++++++++++++ .../chat/code-engine/iframe-click-handler.ts | 274 ++++++++ .../components/chat/code-engine/web-view.tsx | 156 +++-- 3 files changed, 1027 insertions(+), 50 deletions(-) create mode 100644 frontend/src/components/chat/code-engine/componentInspector.tsx create mode 100644 frontend/src/components/chat/code-engine/iframe-click-handler.ts diff --git a/frontend/src/components/chat/code-engine/componentInspector.tsx b/frontend/src/components/chat/code-engine/componentInspector.tsx new file mode 100644 index 00000000..20873e49 --- /dev/null +++ b/frontend/src/components/chat/code-engine/componentInspector.tsx @@ -0,0 +1,647 @@ +import { useState, useEffect, useRef } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Badge } from '@/components/ui/badge'; +import { ScrollArea } from '@/components/ui/scroll-area'; +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 { getElementStyles, updateElementStyle, updateElementContent } from './iframe-click-handler'; + +// Type for component data from custom inspector +type ComponentData = { + id: string; + name: string; + path: string; + line: string; + file: string; + content: { + text?: string; + className?: string; + placeholder?: string; + [key: string]: any; + }; +}; + +// Type for computed styles +type ComputedStyles = { + [key: string]: string; +}; + +export function ComponentInspector() { + const [selectedComponent, setSelectedComponent] = useState(null); + const [activeTab, setActiveTab] = useState('info'); + const [computedStyles, setComputedStyles] = useState(null); + const [customStyles, setCustomStyles] = useState({}); + const [editableContent, setEditableContent] = useState(''); + const [isContentEdited, setIsContentEdited] = useState(false); + const [isStyleEdited, setIsStyleEdited] = useState(false); + const [applyingChanges, setApplyingChanges] = useState(false); + const iframeRef = useRef(null); + + // Get the iframe reference from the web-view + useEffect(() => { + const getIframeRef = () => { + const iframe = document.getElementById('myIframe') as HTMLIFrameElement; + if (iframe) { + iframeRef.current = iframe; + console.log("Iframe reference obtained"); + + // Try to get styles if component is selected + if (selectedComponent) { + console.log("Requesting styles for component:", selectedComponent.id); + getElementStyles(iframe, selectedComponent.id); + } + } else { + console.warn("Couldn't find iframe with id 'myIframe'"); + // Try again in 500ms if iframe not found + setTimeout(getIframeRef, 500); + } + }; + + getIframeRef(); + }, [selectedComponent?.id]); + + // Listen for messages from the iframe + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + if (!event.data || !event.data.type) { + console.warn("Received message with missing type:", event.data); + return; + } + + console.log("ComponentInspector received message:", event.data.type); + + // Handle component click data forwarded from iframe-click-handler + if (event.data.type === 'COMPONENT_CLICK') { + console.log("Processing component click:", event.data.componentData); + setSelectedComponent(event.data.componentData); + setComputedStyles(null); // Reset computed styles + setCustomStyles({}); // Reset custom styles + setIsContentEdited(false); + setIsStyleEdited(false); + + // Get latest content + if (event.data.componentData.content.text) { + setEditableContent(event.data.componentData.content.text); + } else { + setEditableContent(''); + } + } else if (event.data.type === 'ELEMENT_STYLES') { + console.log("Processing element styles response:", event.data.payload); + if (event.data.payload && event.data.payload.success) { + console.log("Received computed styles:", event.data.payload.styles); + setComputedStyles(event.data.payload.styles); + } else { + console.error("Error fetching styles:", event.data.payload?.error || "Unknown error"); + } + } else if (event.data.type === 'ELEMENT_STYLE_UPDATED') { + console.log("Processing style update response:", event.data.payload); + setApplyingChanges(false); + + if (event.data.payload && event.data.payload.success) { + // Update the selected component with new data + setSelectedComponent(prev => { + if (!prev) return prev; + return { + ...prev, + ...event.data.payload.elementData, + }; + }); + + // Display confirmation + console.log('Style updated successfully', event.data.payload.appliedStyles); + setIsStyleEdited(false); + } else { + console.error("Style update failed:", event.data.payload?.error || "Unknown error"); + alert(`Failed to update style: ${event.data.payload?.error || "Unknown error"}`); + } + } else if (event.data.type === 'ELEMENT_CONTENT_UPDATED') { + console.log("Processing content update response:", event.data.payload); + setApplyingChanges(false); + + if (event.data.payload && event.data.payload.success) { + // Update the selected component with new data + setSelectedComponent(prev => { + if (!prev) return prev; + return { + ...prev, + ...event.data.payload.elementData, + content: { + ...prev.content, + text: event.data.payload.appliedContent, + }, + }; + }); + + // Display confirmation + console.log('Content updated successfully'); + setIsContentEdited(false); + } else { + console.error("Content update failed:", event.data.payload?.error || "Unknown error"); + alert(`Failed to update content: ${event.data.payload?.error || "Unknown error"}`); + } + } + }; + + window.addEventListener('message', handleMessage); + return () => window.removeEventListener('message', handleMessage); + }, []); + + // Handle custom style changes + const handleStyleChange = (property: string, value: string) => { + setCustomStyles(prev => ({ + ...prev, + [property]: value, + })); + setIsStyleEdited(true); + }; + + // Apply custom styles + const applyStyles = (event?: React.FormEvent) => { + if (event) { + event.preventDefault(); + } + + if (!selectedComponent || !iframeRef.current) { + console.error('Cannot apply styles: component or iframe not available', { + hasSelectedComponent: !!selectedComponent, + hasIframe: !!iframeRef.current + }); + alert('Cannot apply styles: component or iframe not available'); + return; + } + + // Filter out empty values + const stylesToApply = Object.entries(customStyles) + .filter(([_, value]) => value.trim() !== '') + .reduce((acc, [key, value]) => { + acc[key] = value; + return acc; + }, {} as Record); + + if (Object.keys(stylesToApply).length === 0) { + console.warn('No styles to apply'); + alert('No styles to apply'); + return; + } + + console.log('Applying styles:', stylesToApply); + setApplyingChanges(true); + + try { + updateElementStyle(iframeRef.current, selectedComponent.id, stylesToApply); + } catch (error) { + setApplyingChanges(false); + console.error('Error applying styles:', error); + alert('Failed to apply styles. See console for details.'); + } + }; + + // Apply content changes + const applyContentChanges = (event?: React.FormEvent) => { + if (event) { + event.preventDefault(); + } + + if (!selectedComponent || !iframeRef.current) { + console.error('Cannot apply content: component or iframe not available', { + hasSelectedComponent: !!selectedComponent, + hasIframe: !!iframeRef.current + }); + alert('Cannot apply content: component or iframe not available'); + return; + } + + if (!isContentEdited) { + console.warn('No content changes to apply'); + alert('No content changes to apply'); + return; + } + + console.log('Applying content changes:', editableContent); + setApplyingChanges(true); + + try { + updateElementContent(iframeRef.current, selectedComponent.id, editableContent); + } catch (error) { + setApplyingChanges(false); + console.error('Error applying content:', error); + alert('Failed to apply content. See console for details.'); + } + }; + + if (!selectedComponent) { + return ( + +

+ Click on any component in the preview to inspect it +

+
+ ); + } + + // Parse ID parts (filepath:line:col) for display + const idParts = selectedComponent.id.split(':'); + const filePath = idParts[0] || selectedComponent.path; + const lineNumber = idParts.length > 1 ? idParts[1] : selectedComponent.line; + const colNumber = idParts.length > 2 ? idParts[2] : '0'; + + // Get filename from path + const fileName = selectedComponent.file || filePath.split('/').pop() || 'Unknown'; + + // Parse classes for better display + const classes = selectedComponent.content.className?.split(' ').filter(Boolean) || []; + + return ( + + +
+ + {selectedComponent.name} + + {lineNumber}:{colNumber} + + +
+

+ {selectedComponent.path || filePath} +

+
+ + +
+ + Info + Styles + Classes + Content + +
+ + + {/* Info Tab */} + +
+
+

Component

+

{selectedComponent.name}

+
+
+

File

+

{fileName}

+
+
+

Location

+

Line {lineNumber}

+
+
+

Full Path

+

{selectedComponent.path || filePath}

+
+
+
+ + {/* Styles Tab */} + +
+
+

Edit Styles

+ {isStyleEdited && ( + + )} +
+ +
+ {/* Colors Section */} +
+

Colors

+
+
+ +
+ handleStyleChange('color', e.target.value)} + /> + handleStyleChange('color', e.target.value)} + className="h-8 text-xs flex-1" + /> +
+
+
+ +
+ handleStyleChange('backgroundColor', e.target.value)} + /> + handleStyleChange('backgroundColor', e.target.value)} + className="h-8 text-xs flex-1" + /> +
+
+
+
+ + {/* Typography Section */} +
+

Typography

+
+
+ + handleStyleChange('fontSize', e.target.value)} + className="h-8 text-xs" + placeholder={computedStyles?.fontSize || '16px'} + /> +
+
+ + handleStyleChange('fontWeight', e.target.value)} + className="h-8 text-xs" + placeholder={computedStyles?.fontWeight || 'normal'} + /> +
+
+
+ +
+ {['left', 'center', 'right', 'justify'].map(align => ( + + ))} +
+
+
+ + {/* Spacing Section */} +
+

Spacing

+
Padding
+
+
+ + { + const value = e.target.value; + handleStyleChange('paddingLeft', value); + handleStyleChange('paddingRight', value); + }} + /> +
+
+ + { + const value = e.target.value; + handleStyleChange('paddingTop', value); + handleStyleChange('paddingBottom', value); + }} + /> +
+
+ +
Margin
+
+
+ + { + const value = e.target.value; + handleStyleChange('marginLeft', value); + handleStyleChange('marginRight', value); + }} + /> +
+
+ + { + const value = e.target.value; + handleStyleChange('marginTop', value); + handleStyleChange('marginBottom', value); + }} + /> +
+
+
+ + {/* Size Section */} +
+

Size

+
+
+ + handleStyleChange('width', e.target.value)} + className="h-8 text-xs" + placeholder={computedStyles?.width || 'auto'} + /> +
+
+ + handleStyleChange('height', e.target.value)} + className="h-8 text-xs" + placeholder={computedStyles?.height || 'auto'} + /> +
+
+
+ + {/* Border Section */} +
+

Border

+
+
+ + handleStyleChange('borderWidth', e.target.value)} + className="h-8 text-xs" + placeholder={computedStyles?.borderWidth || '0px'} + /> +
+
+ + +
+
+ +
+ handleStyleChange('borderColor', e.target.value)} + /> + handleStyleChange('borderColor', e.target.value)} + className="h-8 text-xs flex-1" + placeholder={computedStyles?.borderColor || '#000000'} + /> +
+
+
+
+ + handleStyleChange('borderRadius', e.target.value)} + className="h-8 text-xs mt-1" + placeholder={computedStyles?.borderRadius || '0px'} + /> +
+
+ + {/* Hidden submit button for form submission */} + +
+
+
+ + {/* Classes Tab */} + + {classes.length > 0 ? ( +
+ {classes.map((cls, i) => ( + + {cls} + + ))} +
+ ) : ( +

No classes applied

+ )} +
+ + {/* Content Tab */} + +
+
+

Edit Content

+ {isContentEdited && ( + + )} +
+ +
+