"use client" import React from 'react' import { ComponentInstance } from './store' export interface WireframeElement { id: string type: 'rect' | 'circle' | 'text' | 'path' | 'line' | 'group' x: number y: number width: number height: number fill?: string stroke?: string strokeWidth?: number text?: string fontSize?: number fontFamily?: string fontWeight?: string rx?: number ry?: number cx?: number cy?: number r?: number children?: WireframeElement[] } export interface ParsedWireframe { elements: WireframeElement[] width: number height: number deviceType: 'desktop' | 'tablet' | 'mobile' } /** * Converts SVG wireframe data to React components for the component canvas */ export class WireframeToComponentsConverter { private static instance: WireframeToComponentsConverter private componentCounter = 0 static getInstance(): WireframeToComponentsConverter { if (!WireframeToComponentsConverter.instance) { WireframeToComponentsConverter.instance = new WireframeToComponentsConverter() } return WireframeToComponentsConverter.instance } /** * Parse SVG string and extract wireframe elements */ parseSVGToWireframe(svgString: string, deviceType: 'desktop' | 'tablet' | 'mobile' = 'desktop'): ParsedWireframe { try { const parser = new DOMParser() const svgDoc = parser.parseFromString(svgString, 'image/svg+xml') const svgElement = svgDoc.querySelector('svg') if (!svgElement) { throw new Error('No SVG element found') } // Get SVG dimensions const viewBox = svgElement.getAttribute('viewBox') let width = 800 let height = 600 if (viewBox) { const values = viewBox.split(/\s+|,/).map(Number).filter(n => !isNaN(n)) if (values.length >= 4) { width = values[2] height = values[3] } } else { const widthAttr = svgElement.getAttribute('width') const heightAttr = svgElement.getAttribute('height') if (widthAttr && heightAttr) { width = parseFloat(widthAttr.replace(/px|pt|em|rem|%/g, '')) || 800 height = parseFloat(heightAttr.replace(/px|pt|em|rem|%/g, '')) || 600 } } const elements = this.parseSVGElements(svgElement) return { elements, width, height, deviceType } } catch (error) { console.error('Error parsing SVG:', error) return { elements: [], width: 800, height: 600, deviceType } } } /** * Parse SVG elements recursively */ private parseSVGElements(svgElement: Element): WireframeElement[] { const elements: WireframeElement[] = [] Array.from(svgElement.children).forEach((element) => { const tagName = element.tagName.toLowerCase() // Skip non-visual elements if (['defs', 'style', 'title', 'desc', 'metadata', 'clippath', 'mask'].includes(tagName)) { return } const wireframeElement = this.parseElement(element) if (wireframeElement) { elements.push(wireframeElement) } }) return elements } /** * Parse individual SVG element */ private parseElement(element: Element): WireframeElement | null { const tagName = element.tagName.toLowerCase() const id = element.getAttribute('id') || `element-${++this.componentCounter}` // Check visibility const visibility = element.getAttribute('visibility') const display = element.getAttribute('display') const opacity = element.getAttribute('opacity') if (visibility === 'hidden' || display === 'none' || opacity === '0') { return null } switch (tagName) { case 'rect': return this.parseRect(element as SVGRectElement, id) case 'circle': return this.parseCircle(element as SVGCircleElement, id) case 'ellipse': return this.parseEllipse(element as SVGEllipseElement, id) case 'text': return this.parseText(element as SVGTextElement, id) case 'path': return this.parsePath(element as SVGPathElement, id) case 'line': return this.parseLine(element as SVGLineElement, id) case 'g': return this.parseGroup(element, id) default: return null } } private parseRect(element: SVGRectElement, id: string): WireframeElement { const x = parseFloat(element.getAttribute('x') || '0') const y = parseFloat(element.getAttribute('y') || '0') const width = parseFloat(element.getAttribute('width') || '100') const height = parseFloat(element.getAttribute('height') || '100') const fill = element.getAttribute('fill') || 'none' const stroke = element.getAttribute('stroke') || '#000000' const strokeWidth = parseFloat(element.getAttribute('stroke-width') || '1') const rx = parseFloat(element.getAttribute('rx') || '0') const ry = parseFloat(element.getAttribute('ry') || '0') return { id, type: 'rect', x, y, width, height, fill, stroke, strokeWidth, rx, ry } } private parseCircle(element: SVGCircleElement, id: string): WireframeElement { const cx = parseFloat(element.getAttribute('cx') || '0') const cy = parseFloat(element.getAttribute('cy') || '0') const r = parseFloat(element.getAttribute('r') || '50') const fill = element.getAttribute('fill') || 'none' const stroke = element.getAttribute('stroke') || '#000000' const strokeWidth = parseFloat(element.getAttribute('stroke-width') || '1') return { id, type: 'circle', x: cx - r, y: cy - r, width: r * 2, height: r * 2, fill, stroke, strokeWidth, cx, cy, r } } private parseEllipse(element: SVGEllipseElement, id: string): WireframeElement { const cx = parseFloat(element.getAttribute('cx') || '0') const cy = parseFloat(element.getAttribute('cy') || '0') const rx = parseFloat(element.getAttribute('rx') || '50') const ry = parseFloat(element.getAttribute('ry') || '30') const fill = element.getAttribute('fill') || 'none' const stroke = element.getAttribute('stroke') || '#000000' const strokeWidth = parseFloat(element.getAttribute('stroke-width') || '1') return { id, type: 'circle', x: cx - rx, y: cy - ry, width: rx * 2, height: ry * 2, fill, stroke, strokeWidth, cx, cy, r: rx } } private parseText(element: SVGTextElement, id: string): WireframeElement { const x = parseFloat(element.getAttribute('x') || '0') const y = parseFloat(element.getAttribute('y') || '0') const text = element.textContent || '' const fill = element.getAttribute('fill') || '#000000' const fontSize = parseFloat(element.getAttribute('font-size') || '16') const fontFamily = element.getAttribute('font-family') || 'sans-serif' const fontWeight = element.getAttribute('font-weight') || 'normal' // Estimate text dimensions const estimatedWidth = text.length * (fontSize * 0.6) const estimatedHeight = fontSize return { id, type: 'text', x, y, width: estimatedWidth, height: estimatedHeight, fill, text, fontSize, fontFamily, fontWeight } } private parsePath(element: SVGPathElement, id: string): WireframeElement { const d = element.getAttribute('d') || '' const fill = element.getAttribute('fill') || 'none' const stroke = element.getAttribute('stroke') || '#000000' const strokeWidth = parseFloat(element.getAttribute('stroke-width') || '1') // For paths, we'll create a bounding box // This is a simplified approach - in a real implementation, you'd parse the path data const bounds = this.getPathBounds(d) return { id, type: 'path', x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height, fill, stroke, strokeWidth } } private parseLine(element: SVGLineElement, id: string): WireframeElement { const x1 = parseFloat(element.getAttribute('x1') || '0') const y1 = parseFloat(element.getAttribute('y1') || '0') const x2 = parseFloat(element.getAttribute('x2') || '0') const y2 = parseFloat(element.getAttribute('y2') || '0') const stroke = element.getAttribute('stroke') || '#000000' const strokeWidth = parseFloat(element.getAttribute('stroke-width') || '1') const x = Math.min(x1, x2) const y = Math.min(y1, y2) const width = Math.abs(x2 - x1) const height = Math.abs(y2 - y1) return { id, type: 'line', x, y, width, height, stroke, strokeWidth } } private parseGroup(element: Element, id: string): WireframeElement { const children = this.parseSVGElements(element) // Calculate group bounds let minX = Infinity let minY = Infinity let maxX = -Infinity let maxY = -Infinity children.forEach(child => { minX = Math.min(minX, child.x) minY = Math.min(minY, child.y) maxX = Math.max(maxX, child.x + child.width) maxY = Math.max(maxY, child.y + child.height) }) return { id, type: 'group', x: minX === Infinity ? 0 : minX, y: minY === Infinity ? 0 : minY, width: maxX === -Infinity ? 0 : maxX - minX, height: maxY === -Infinity ? 0 : maxY - minY, children } } private getPathBounds(d: string): { x: number; y: number; width: number; height: number } { // Simplified path bounds calculation // In a real implementation, you'd parse the path data properly return { x: 0, y: 0, width: 100, height: 50 } } /** * Convert wireframe elements to React components */ convertToComponents(wireframe: ParsedWireframe): ComponentInstance[] { const components: ComponentInstance[] = [] wireframe.elements.forEach((element, index) => { const component = this.elementToComponent(element, index) if (component) { components.push(component) } }) return components } /** * Convert a single wireframe element to a React component */ private elementToComponent(element: WireframeElement, index: number): ComponentInstance | null { const baseProps = { x: element.x, y: element.y, width: element.width, height: element.height, fill: element.fill, stroke: element.stroke, strokeWidth: element.strokeWidth } switch (element.type) { case 'rect': return { id: `wireframe-${element.id}-${index}`, type: 'wireframe-rect', props: { ...baseProps, rx: element.rx || 0, ry: element.ry || 0 }, position: { x: element.x, y: element.y }, size: { width: element.width, height: element.height } } case 'circle': return { id: `wireframe-${element.id}-${index}`, type: 'wireframe-circle', props: { ...baseProps, cx: element.cx || element.x + element.width / 2, cy: element.cy || element.y + element.height / 2, r: element.r || Math.min(element.width, element.height) / 2 }, position: { x: element.x, y: element.y }, size: { width: element.width, height: element.height } } case 'text': return { id: `wireframe-${element.id}-${index}`, type: 'wireframe-text', props: { ...baseProps, text: element.text || '', fontSize: element.fontSize || 16, fontFamily: element.fontFamily || 'sans-serif', fontWeight: element.fontWeight || 'normal' }, position: { x: element.x, y: element.y }, size: { width: element.width, height: element.height } } case 'line': return { id: `wireframe-${element.id}-${index}`, type: 'wireframe-line', props: baseProps, position: { x: element.x, y: element.y }, size: { width: element.width, height: element.height } } case 'path': return { id: `wireframe-${element.id}-${index}`, type: 'wireframe-path', props: baseProps, position: { x: element.x, y: element.y }, size: { width: element.width, height: element.height } } case 'group': // For groups, we'll create a container component return { id: `wireframe-${element.id}-${index}`, type: 'wireframe-group', props: { ...baseProps, children: element.children || [] }, position: { x: element.x, y: element.y }, size: { width: element.width, height: element.height } } default: return null } } /** * Convert wireframe to a single SVG component for display */ convertToSVGComponent(wireframe: ParsedWireframe): ComponentInstance { const timestamp = Date.now() const randomId = Math.random().toString(36).substr(2, 9) return { id: `wireframe-svg-${timestamp}-${randomId}`, type: 'wireframe-svg', props: { elements: wireframe.elements, width: wireframe.width, height: wireframe.height, deviceType: wireframe.deviceType }, position: { x: 0, y: 0 }, size: { width: wireframe.width, height: wireframe.height } } } } export const wireframeConverter = WireframeToComponentsConverter.getInstance()