diff --git a/src/launchpad/artifacts/apple/zipped_xcarchive.py b/src/launchpad/artifacts/apple/zipped_xcarchive.py index 8f4a709b..2fc915f9 100644 --- a/src/launchpad/artifacts/apple/zipped_xcarchive.py +++ b/src/launchpad/artifacts/apple/zipped_xcarchive.py @@ -34,6 +34,7 @@ class AssetCatalogElement: idiom: str | None = None colorspace: str | None = None content_hash: str | None = None + scale: int | None = None @dataclass @@ -479,6 +480,7 @@ def _parse_asset_element(self, item: dict[str, Any], parent_path: Path) -> Asset idiom = item.get("idiom") colorspace = item.get("colorspace") content_hash = item.get("contentHash") + scale = item.get("scale") file_extension = Path(filename).suffix.lower() if filename and file_extension in {".png", ".jpg", ".jpeg", ".heic", ".heif", ".pdf", ".svg"}: @@ -501,6 +503,7 @@ def _parse_asset_element(self, item: dict[str, Any], parent_path: Path) -> Asset idiom=idiom, colorspace=colorspace, content_hash=content_hash, + scale=scale, ) def _parse_and_cache_all_binaries(self, binary_paths: List[Path]) -> None: diff --git a/src/launchpad/size/models/common.py b/src/launchpad/size/models/common.py index bbfd9442..10193fea 100644 --- a/src/launchpad/size/models/common.py +++ b/src/launchpad/size/models/common.py @@ -87,6 +87,7 @@ class FileInfo(BaseModel): # Asset catalog specific fields idiom: str | None = Field(default=None, exclude=True, description="Device idiom for asset catalog images") colorspace: str | None = Field(default=None, exclude=True, description="Color space for asset catalog images") + scale: int | None = Field(default=None, exclude=True, description="Scale factor for asset catalog images") class ComponentType(IntEnum): @@ -153,6 +154,6 @@ class BaseAnalysisResults(BaseModel): def to_dict(self) -> Dict[str, Any]: """Convert to dictionary with serializable datetime.""" - data = self.model_dump() + data = self.model_dump(exclude_none=True) data["generated_at"] = self.generated_at.isoformat() return data diff --git a/src/launchpad/size/models/treemap.py b/src/launchpad/size/models/treemap.py index c71122c0..96e5910a 100644 --- a/src/launchpad/size/models/treemap.py +++ b/src/launchpad/size/models/treemap.py @@ -54,6 +54,14 @@ class TreemapType(str, Enum): UNMAPPED = "unmapped" +class TreemapElementMisc(BaseModel): + """Miscellaneous metadata for treemap elements.""" + + model_config = ConfigDict(frozen=True) + + scale: int | None = Field(None, description="Scale factor for asset catalog images (1, 2, 3)") + + # Mapping from file types to TreemapType FILE_TYPE_TO_TREEMAP_TYPE: dict[str, TreemapType] = { # Binary types @@ -120,6 +128,7 @@ class TreemapElement(BaseModel): is_dir: bool = Field(False, description="Whether this element represents a directory") """ Some files (like zip files) are not directories but have children. """ children: List[TreemapElement] = Field(default_factory=list, description="Child elements") + misc: TreemapElementMisc | None = Field(None, description="Optional miscellaneous data for this element") class TreemapResults(BaseModel): diff --git a/src/launchpad/size/treemap/default_file_element_builder.py b/src/launchpad/size/treemap/default_file_element_builder.py index a464d2f3..d8d8397b 100644 --- a/src/launchpad/size/treemap/default_file_element_builder.py +++ b/src/launchpad/size/treemap/default_file_element_builder.py @@ -1,7 +1,7 @@ import os from launchpad.size.models.common import FileInfo -from launchpad.size.models.treemap import TreemapElement +from launchpad.size.models.treemap import TreemapElement, TreemapElementMisc from launchpad.size.treemap.treemap_element_builder import TreemapElementBuilder from launchpad.utils.file_utils import to_nearest_block_size @@ -9,6 +9,11 @@ class DefaultFileElementBuilder(TreemapElementBuilder): def build_element(self, file_info: FileInfo, display_name: str) -> TreemapElement: size = to_nearest_block_size(file_info.size, self.filesystem_block_size) + + misc = None + if file_info.scale is not None: + misc = TreemapElementMisc(scale=file_info.scale) + return TreemapElement( name=display_name, size=size, @@ -16,4 +21,5 @@ def build_element(self, file_info: FileInfo, display_name: str) -> TreemapElemen path=file_info.path, is_dir=False, children=[self.build_element(child, os.path.basename(child.path)) for child in file_info.children], + misc=misc, ) diff --git a/src/launchpad/size/utils/file_analysis.py b/src/launchpad/size/utils/file_analysis.py index 5321c00d..8bc26ff8 100644 --- a/src/launchpad/size/utils/file_analysis.py +++ b/src/launchpad/size/utils/file_analysis.py @@ -293,6 +293,7 @@ def _analyze_asset_catalog(xcarchive: ZippedXCArchive, relative_path: Path) -> L children=[], idiom=element.idiom, colorspace=element.colorspace, + scale=element.scale, ) ) return result diff --git a/web/src/types/treemap.ts b/web/src/types/treemap.ts index d65e530d..f69b9c3f 100644 --- a/web/src/types/treemap.ts +++ b/web/src/types/treemap.ts @@ -42,6 +42,11 @@ export enum TreemapType { UNMAPPED = "unmapped", } +export interface TreemapElementMisc { + /** Scale factor for asset catalog images (1, 2, 3) */ + scale?: number; +} + export interface TreemapElement { /** Display name of the element */ name: string; @@ -53,6 +58,8 @@ export interface TreemapElement { path?: string; /** Whether this element represents a directory */ is_dir: boolean; + /** Optional miscellaneous data for this element */ + misc?: TreemapElementMisc; /** Child elements */ children: TreemapElement[]; }