{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "image-editor-metadata",
  "title": "Metadata",
  "description": "File info and EXIF of the open image.",
  "dependencies": [
    "photocn@^0.1.1"
  ],
  "registryDependencies": [
    "alert",
    "badge",
    "field",
    "separator",
    "table"
  ],
  "files": [
    {
      "path": "src/registry/image-editor/metadata-panel.tsx",
      "content": "\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport type { UseMiniPhotoEditorResult } from \"photocn/hooks\";\nimport { useImageEditor } from \"photocn/react\";\n\nimport { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Field, FieldContent, FieldGroup, FieldTitle } from \"@/components/ui/field\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableRow,\n} from \"@/components/ui/table\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface MetadataPanelProps {\n  editor: UseMiniPhotoEditorResult;\n  title?: ReactNode;\n  className?: string;\n}\n\ninterface MetadataRow {\n  label: string;\n  value: ReactNode;\n}\n\nexport function MetadataPanel({\n  editor,\n  title = \"Metadata\",\n  className,\n}: MetadataPanelProps) {\n  const rows = metadataRows(editor);\n  const exifError = editor.exif.status === \"error\" ? editor.exif.error : null;\n\n  return (\n    <FieldGroup className={cn(\"gap-3 p-3\", className)} data-slot=\"metadata-panel\">\n      <Field orientation=\"horizontal\">\n        <FieldContent>\n          <div className=\"flex min-w-0 items-center justify-between gap-2\">\n            <FieldTitle className=\"truncate\">{title}</FieldTitle>\n            <Badge variant={editor.exif.status === \"loaded\" ? \"secondary\" : \"outline\"}>\n              {editor.exif.status === \"loading\"\n                ? \"Reading\"\n                : editor.exif.status === \"loaded\"\n                  ? \"EXIF ready\"\n                  : \"File\"}\n            </Badge>\n          </div>\n        </FieldContent>\n      </Field>\n\n      {exifError ? (\n        <Alert>\n          <AlertDescription>{errorMessage(exifError)}</AlertDescription>\n        </Alert>\n      ) : null}\n\n      <Separator />\n\n      {rows.length ? (\n        <Table>\n          <TableBody>\n            {rows.map((row) => (\n              <TableRow key={row.label}>\n                <TableCell className=\"w-28 align-top text-xs text-muted-foreground\">\n                  {row.label}\n                </TableCell>\n                <TableCell className=\"whitespace-normal break-words text-xs\">\n                  {row.value}\n                </TableCell>\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n      ) : (\n        <p className=\"text-xs text-muted-foreground\">No image metadata loaded.</p>\n      )}\n    </FieldGroup>\n  );\n}\n\nfunction metadataRows(editor: UseMiniPhotoEditorResult): MetadataRow[] {\n  const { fileInfo, image, blob } = editor.imageInput;\n  const metadata = editor.exif.metadata;\n  const rows: MetadataRow[] = [];\n\n  addRow(rows, \"Name\", fileInfo?.name);\n  addRow(rows, \"Type\", fileInfo?.type || blob?.type);\n  addRow(rows, \"Size\", fileInfo?.size ? formatBytes(fileInfo.size) : undefined);\n  addRow(rows, \"Dimensions\", image ? `${image.naturalWidth} x ${image.naturalHeight}` : undefined);\n  addRow(rows, \"Modified\", fileInfo?.lastModified ? formatDate(fileInfo.lastModified) : undefined);\n  addRow(rows, \"Format\", metadata?.format);\n  addRow(rows, \"Color\", colorProfile(metadata?.icc));\n  addRow(rows, \"Captured\", exifTagValue(metadata?.exif?.DateTimeOriginal));\n  addRow(rows, \"Camera\", cameraLabel(metadata?.tiff?.Make, metadata?.tiff?.Model));\n  addRow(rows, \"Lens\", exifTagValue(metadata?.exif?.LensModel));\n  addRow(rows, \"GPS\", gpsLabel(metadata?.gps));\n\n  return rows;\n}\n\nfunction addRow(rows: MetadataRow[], label: string, value: ReactNode) {\n  if (value === undefined || value === null || value === \"\") return;\n  rows.push({ label, value });\n}\n\nfunction exifTagValue(tag: { hvalue?: unknown; value?: unknown } | undefined): string | undefined {\n  return formatMetadataValue(tag?.hvalue ?? tag?.value);\n}\n\nfunction cameraLabel(\n  makeTag: { hvalue?: unknown; value?: unknown } | undefined,\n  modelTag: { hvalue?: unknown; value?: unknown } | undefined,\n): string | undefined {\n  const make = exifTagValue(makeTag);\n  const model = exifTagValue(modelTag);\n\n  if (make && model) return `${make} ${model}`;\n  return make ?? model;\n}\n\nfunction gpsLabel(gps: Record<string, { hvalue?: unknown; value?: unknown }> | undefined): string | undefined {\n  const longitude = exifTagValue(gps?.GPSLongitude);\n  const latitude = exifTagValue(gps?.GPSLatitude);\n\n  if (longitude && latitude) return `${latitude}, ${longitude}`;\n  return latitude ?? longitude;\n}\n\nfunction colorProfile(icc: Record<string, unknown> | undefined): string | undefined {\n  const profile = icc?.ColorProfile;\n  if (Array.isArray(profile)) return formatMetadataValue(profile[0]);\n  return formatMetadataValue(profile);\n}\n\nfunction formatMetadataValue(value: unknown): string | undefined {\n  if (value === undefined || value === null || value === \"\") return undefined;\n  if (Array.isArray(value)) return value.map(formatMetadataValue).filter(Boolean).join(\", \");\n  if (value instanceof Date) return value.toLocaleString();\n  if (typeof value === \"object\") return JSON.stringify(value);\n  return String(value);\n}\n\nfunction formatDate(value: number): string {\n  return new Date(value).toLocaleString();\n}\n\nfunction formatBytes(size: number): string {\n  if (!Number.isFinite(size) || size <= 0) return \"0 B\";\n\n  const units = [\"B\", \"KB\", \"MB\", \"GB\"] as const;\n  let value = size;\n  let unitIndex = 0;\n\n  while (value >= 1024 && unitIndex < units.length - 1) {\n    value /= 1024;\n    unitIndex += 1;\n  }\n\n  return `${value.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;\n}\n\nfunction errorMessage(error: unknown): string {\n  if (error instanceof Error) return error.message;\n  if (typeof error === \"string\") return error;\n  return \"Unable to read metadata.\";\n}\n\nexport type ImageEditorMetadataProps = Omit<MetadataPanelProps, \"editor\">;\n\n/** File info and EXIF of the open image, from the nearest `<ImageEditorProvider>`. */\nexport function ImageEditorMetadata(props: ImageEditorMetadataProps) {\n  const editor = useImageEditor();\n  return <MetadataPanel editor={editor.engine} {...props} />;\n}\n\n",
      "type": "registry:component",
      "target": "components/image-editor/metadata-panel.tsx"
    }
  ],
  "categories": [
    "image-editor"
  ],
  "type": "registry:component"
}