Skip to content

Commit 2915a8d

Browse files
committed
feat(#2633): add support for base64 encoded images
1 parent 1e78a63 commit 2915a8d

File tree

6 files changed

+211
-46
lines changed

6 files changed

+211
-46
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ changes.
1212

1313
### Added
1414

15-
-
15+
- Add support for base64 encoded images [Issue 2633](https://github.com/IntersectMBO/govtool/issues/2633)
1616

1717
### Fixed
1818

govtool/frontend/src/components/molecules/DataMissingHeader.tsx

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { Avatar, Box, SxProps } from "@mui/material";
22

33
import { Typography } from "@atoms";
44
import { MetadataValidationStatus } from "@models";
5-
import { getMetadataDataMissingStatusTranslation } from "@/utils";
5+
import {
6+
getBase64ImageDetails,
7+
getMetadataDataMissingStatusTranslation,
8+
} from "@/utils";
69
import { ICONS } from "@/consts";
710

811
type DataMissingHeaderProps = {
@@ -19,54 +22,62 @@ export const DataMissingHeader = ({
1922
titleStyle,
2023
isDRep,
2124
image,
22-
}: DataMissingHeaderProps) => (
23-
<Box
24-
sx={{
25-
display: "grid",
26-
gridTemplateColumns: "1fr auto",
27-
gap: 2,
28-
alignItems: "center",
29-
mb: 2,
30-
}}
31-
data-testid="governance-action-details-card-header"
32-
>
25+
}: DataMissingHeaderProps) => {
26+
const base64Image = getBase64ImageDetails(image ?? "");
27+
28+
return (
3329
<Box
3430
sx={{
35-
flexDirection: {
36-
xxs: "column",
37-
md: "row",
38-
},
39-
alignItems: {
40-
md: "center",
41-
},
42-
display: "flex",
31+
display: "grid",
32+
gridTemplateColumns: "1fr auto",
33+
gap: 2,
34+
alignItems: "center",
35+
mb: 2,
4336
}}
37+
data-testid="governance-action-details-card-header"
4438
>
45-
{isDRep && (
46-
<Avatar
47-
alt="drep-image"
48-
src={image || ICONS.defaultDRepIcon}
49-
sx={{ width: 80, height: 80 }}
50-
data-testid="drep-image"
51-
/>
52-
)}
53-
<Typography
39+
<Box
5440
sx={{
55-
...(isDRep && { ml: { md: 3 } }),
56-
...(isDRep && { mt: { xxs: 2, md: 0 } }),
57-
textOverflow: "ellipsis",
58-
fontWeight: 600,
59-
...(isDataMissing && { color: "errorRed" }),
60-
...titleStyle,
41+
flexDirection: {
42+
xxs: "column",
43+
md: "row",
44+
},
45+
alignItems: {
46+
md: "center",
47+
},
48+
display: "flex",
6149
}}
62-
variant="title2"
6350
>
64-
{(isDataMissing &&
65-
getMetadataDataMissingStatusTranslation(
66-
isDataMissing as MetadataValidationStatus,
67-
)) ||
68-
title}
69-
</Typography>
51+
{isDRep && (
52+
<Avatar
53+
alt="drep-image"
54+
src={
55+
(base64Image.isValidBase64Image
56+
? `${base64Image.base64Prefix}${image}`
57+
: image) || ICONS.defaultDRepIcon
58+
}
59+
sx={{ width: 80, height: 80 }}
60+
data-testid="drep-image"
61+
/>
62+
)}
63+
<Typography
64+
sx={{
65+
...(isDRep && { ml: { md: 3 } }),
66+
...(isDRep && { mt: { xxs: 2, md: 0 } }),
67+
textOverflow: "ellipsis",
68+
fontWeight: 600,
69+
...(isDataMissing && { color: "errorRed" }),
70+
...titleStyle,
71+
}}
72+
variant="title2"
73+
>
74+
{(isDataMissing &&
75+
getMetadataDataMissingStatusTranslation(
76+
isDataMissing as MetadataValidationStatus,
77+
)) ||
78+
title}
79+
</Typography>
80+
</Box>
7081
</Box>
71-
</Box>
72-
);
82+
);
83+
};

govtool/frontend/src/components/organisms/DRepCard.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
correctDRepDirectoryFormat,
1212
ellipsizeText,
1313
encodeCIP129Identifier,
14+
getBase64ImageDetails,
1415
getMetadataDataMissingStatusTranslation,
1516
} from "@utils";
1617

@@ -64,6 +65,8 @@ export const DRepCard = ({
6465
bech32Prefix: isScriptBased ? "drep_script" : "drep",
6566
});
6667

68+
const base64Image = getBase64ImageDetails(image ?? "");
69+
6770
return (
6871
<Card
6972
{...(isMe && {
@@ -119,7 +122,11 @@ export const DRepCard = ({
119122
<Box flexDirection="row" minWidth={0} display="flex">
120123
<Avatar
121124
alt="drep-image"
122-
src={image || ICONS.defaultDRepIcon}
125+
src={
126+
(base64Image.isValidBase64Image
127+
? `${base64Image.base64Prefix}${image}`
128+
: image) || ICONS.defaultDRepIcon
129+
}
123130
data-testid="drep-image"
124131
/>
125132
<Box
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* Checks if a given string is a base64-encoded image or SVG and returns its details.
3+
*
4+
* @param str - The string to check.
5+
* @returns An object with the MIME type, base64 prefix, and validity flag.
6+
*/
7+
export function getBase64ImageDetails(str: string): {
8+
type: string | null;
9+
base64Prefix: string | null;
10+
isValidBase64Image: boolean;
11+
} {
12+
if (!str || typeof str !== "string") {
13+
return {
14+
type: null,
15+
base64Prefix: null,
16+
isValidBase64Image: false,
17+
};
18+
}
19+
20+
try {
21+
const decoded = atob(str);
22+
23+
if (decoded.trim().startsWith("<?xml") || decoded.includes("<svg")) {
24+
return {
25+
type: "svg+xml",
26+
base64Prefix: "data:image/svg+xml;base64,",
27+
isValidBase64Image: true,
28+
};
29+
}
30+
31+
const magicNumbers = decoded.slice(0, 4);
32+
let type: string | null = null;
33+
34+
switch (magicNumbers) {
35+
case "\x89PNG":
36+
type = "png";
37+
break;
38+
case "\xFF\xD8\xFF":
39+
type = "jpeg";
40+
break;
41+
case "GIF8":
42+
type = "gif";
43+
break;
44+
default:
45+
type = null;
46+
}
47+
48+
if (type) {
49+
return {
50+
type,
51+
base64Prefix: `data:image/${type};base64,`,
52+
isValidBase64Image: true,
53+
};
54+
}
55+
} catch {
56+
return {
57+
type: null,
58+
base64Prefix: null,
59+
isValidBase64Image: false,
60+
};
61+
}
62+
63+
return {
64+
type: null,
65+
base64Prefix: null,
66+
isValidBase64Image: false,
67+
};
68+
}

govtool/frontend/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ export * from "./setProtocolParameterUpdate";
3636
export * from "./testIdFromLabel";
3737
export * from "./uniqBy";
3838
export * from "./wait";
39+
export * from "./getBase64ImageDetails";

0 commit comments

Comments
 (0)