File size: 6,075 Bytes
cfec14d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"""
Figma API Client
Handles communication with Figma API to extract design screenshots.
"""

import requests
from typing import Dict, List, Tuple, Optional
from pathlib import Path


class FigmaClient:
    """Client for interacting with Figma API."""
    
    BASE_URL = "https://api.figma.com/v1"
    
    def __init__(self, access_token: str):
        self.access_token = access_token
        self.headers = {
            "X-Figma-Token": access_token
        }
    
    def get_file(self, file_key: str) -> Dict:
        """Get file metadata from Figma."""
        url = f"{self.BASE_URL}/files/{file_key}"
        response = requests.get(url, headers=self.headers)
        response.raise_for_status()
        return response.json()
    
    def get_frame_nodes(self, file_key: str) -> List[Dict]:
        """
        Get all top-level frames from the Figma file.
        Returns frames that match viewport patterns (Desktop/Mobile).
        """
        file_data = self.get_file(file_key)
        frames = []
        
        # Navigate through document -> pages -> frames
        document = file_data.get("document", {})
        for page in document.get("children", []):
            if page.get("type") == "CANVAS":
                for child in page.get("children", []):
                    if child.get("type") == "FRAME":
                        frame_name = child.get("name", "")
                        frame_id = child.get("id", "")
                        bounds = child.get("absoluteBoundingBox", {})
                        
                        # Determine viewport type from name
                        viewport = None
                        if "desktop" in frame_name.lower() or bounds.get("width", 0) >= 1000:
                            viewport = "desktop"
                        elif "mobile" in frame_name.lower() or bounds.get("width", 0) <= 500:
                            viewport = "mobile"
                        
                        if viewport:
                            frames.append({
                                "id": frame_id,
                                "name": frame_name,
                                "viewport": viewport,
                                "width": bounds.get("width", 0),
                                "height": bounds.get("height", 0)
                            })
        
        return frames
    
    def export_frame(
        self, 
        file_key: str, 
        frame_id: str, 
        output_path: str,
        scale: float = 1.0,
        format: str = "png"
    ) -> Tuple[str, Dict[str, int]]:
        """
        Export a frame as an image.
        
        Args:
            file_key: Figma file key
            frame_id: Node ID of the frame to export
            output_path: Where to save the image
            scale: Export scale (1.0 = actual size, 0.5 = half size)
            format: Image format (png, jpg, svg, pdf)
            
        Returns:
            Tuple of (saved_path, dimensions_dict)
        """
        # Get export URL
        url = f"{self.BASE_URL}/images/{file_key}"
        params = {
            "ids": frame_id,
            "scale": scale,
            "format": format
        }
        
        response = requests.get(url, headers=self.headers, params=params)
        response.raise_for_status()
        
        data = response.json()
        image_url = data.get("images", {}).get(frame_id)
        
        if not image_url:
            raise ValueError(f"Could not get export URL for frame {frame_id}")
        
        # Download the image
        img_response = requests.get(image_url)
        img_response.raise_for_status()
        
        # Save to file
        Path(output_path).parent.mkdir(parents=True, exist_ok=True)
        with open(output_path, "wb") as f:
            f.write(img_response.content)
        
        # Get image dimensions
        from PIL import Image
        with Image.open(output_path) as img:
            width, height = img.size
        
        return output_path, {"width": width, "height": height}
    
    def export_frames_for_comparison(
        self,
        file_key: str,
        output_dir: str,
        execution_id: str
    ) -> Tuple[Dict[str, str], Dict[str, Dict[str, int]]]:
        """
        Export all relevant frames for UI comparison.
        
        Automatically finds Desktop and Mobile frames and exports them
        at 1x scale (not 2x) for proper comparison with website screenshots.
        
        Returns:
            Tuple of (screenshot_paths, dimensions)
        """
        frames = self.get_frame_nodes(file_key)
        screenshots = {}
        dimensions = {}
        
        # Group by viewport, prefer larger frames
        viewport_frames = {}
        for frame in frames:
            viewport = frame["viewport"]
            if viewport not in viewport_frames:
                viewport_frames[viewport] = frame
            elif frame["width"] * frame["height"] > viewport_frames[viewport]["width"] * viewport_frames[viewport]["height"]:
                viewport_frames[viewport] = frame
        
        # Export each viewport
        for viewport, frame in viewport_frames.items():
            print(f"  📥 Exporting frame: {frame['name']} ({frame['width']}px width)")
            print(f"     Frame ID: {frame['id']}")
            print(f"     Dimensions: {frame['width']}x{frame['height']}")
            
            output_path = f"{output_dir}/{viewport}_{execution_id}.png"
            
            # Export at scale 1.0 (actual design size)
            # Note: Figma often has designs at 2x, we'll handle normalization in comparison
            saved_path, dims = self.export_frame(
                file_key,
                frame["id"],
                output_path,
                scale=1.0  # Export at 1x to get actual design dimensions
            )
            
            screenshots[viewport] = saved_path
            dimensions[viewport] = dims
            print(f"  ✓ Exported: {saved_path}")
        
        return screenshots, dimensions