Skip to content

Commit f7dc1eb

Browse files
Merge pull request #2163 from AletheiaFact/feature/show-report-button-and-image-display
feat: add review button for images and fix SVG centralization
2 parents e4eb2ba + b6e9519 commit f7dc1eb

File tree

7 files changed

+261
-97
lines changed

7 files changed

+261
-97
lines changed

public/locales/en/claim.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,7 @@
3838
"hideSuccess": "Claim hidden succesfully",
3939
"hideError": "Error while hidden claim",
4040
"unhideSuccess": "Claim unhide succesfully",
41-
"unhideError": "Error while unhide claim"
41+
"unhideError": "Error while unhide claim",
42+
"reviewImageButton": "Review Image",
43+
"openReportButton": "View Report"
4244
}

public/locales/pt/claim.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,7 @@
3838
"hideSuccess": "Afirmação escondida com sucesso",
3939
"hideError": "Erro ao esconder a afirmação",
4040
"unhideSuccess": "Afirmação pública com sucesso",
41-
"unhideError": "Erro ao mostrar a afirmação"
41+
"unhideError": "Erro ao mostrar a afirmação",
42+
"reviewImageButton": "Revisar Imagem",
43+
"openReportButton": "Ver Relatório"
4244
}

src/components/Claim/ClaimContentDisplay.tsx

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import React from "react";
2-
import { useDispatch } from "react-redux";
32

4-
import ImageApi from "../../api/image";
5-
import actions from "../../store/actions";
6-
import { useAppSelector } from "../../store/store";
7-
import ReviewedImage from "../ReviewedImage";
3+
import ClaimImageBody from "./ClaimImageBody";
84
import ClaimSpeechBody from "./ClaimSpeechBody";
95

106
interface ClaimContentDisplayProps {
@@ -22,35 +18,20 @@ const ClaimContentDisplay: React.FC<ClaimContentDisplayProps> = ({
2218
showHighlights,
2319
dispatchPersonalityAndClaim,
2420
}) => {
25-
const { selectedContent } = useAppSelector((state) => state);
26-
const dispatch = useDispatch();
2721
const imageUrl = claimContent.content;
2822
const paragraphs = Array.isArray(claimContent)
2923
? claimContent
3024
: [claimContent];
3125

32-
const handleClickOnImage = () => {
33-
ImageApi.getImageTopicsByDatahash(selectedContent?.data_hash)
34-
.then((image) => {
35-
dispatch(actions.setSelectContent(image));
36-
})
37-
.catch((e) => e);
38-
dispatch(actions.openReviewDrawer());
39-
};
40-
4126
return (
4227
<>
4328
{isImage ? (
44-
<div
45-
style={{ paddingBottom: "20px", cursor: "pointer" }}
46-
onClick={handleClickOnImage}
47-
>
48-
<ReviewedImage
49-
imageUrl={imageUrl}
50-
title={title}
51-
classification={claimContent?.props?.classification}
52-
/>
53-
</div>
29+
<ClaimImageBody
30+
imageUrl={imageUrl}
31+
title={title}
32+
classification={claimContent?.props?.classification}
33+
dataHash={claimContent?.data_hash}
34+
/>
5435
) : (
5536
<cite style={{ fontStyle: "normal" }}>
5637
<ClaimSpeechBody
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import React from "react";
2+
import { Box } from "@mui/material";
3+
import { useTranslation } from "next-i18next";
4+
import { useDispatch } from "react-redux";
5+
6+
import ImageApi from "../../api/image";
7+
import actions from "../../store/actions";
8+
import { useAppSelector } from "../../store/store";
9+
import ReviewedImage from "../ReviewedImage";
10+
import { ClassificationEnum } from "../../types/enums";
11+
import AletheiaButton from "../Button";
12+
13+
interface ClaimImageBodyProps {
14+
imageUrl: string;
15+
title: string;
16+
classification?: keyof typeof ClassificationEnum;
17+
dataHash: string;
18+
}
19+
20+
const ClaimImageBody: React.FC<ClaimImageBodyProps> = ({
21+
imageUrl,
22+
title,
23+
classification,
24+
dataHash,
25+
}) => {
26+
const dispatch = useDispatch();
27+
const { t } = useTranslation();
28+
const { selectedContent } = useAppSelector((state) => state);
29+
30+
const handleClickOnButton = (): void => {
31+
ImageApi.getImageTopicsByDatahash(
32+
dataHash || selectedContent?.data_hash
33+
)
34+
.then((image) => {
35+
dispatch(actions.setSelectContent(image));
36+
})
37+
.catch((error) => {
38+
console.error("Failed to fetch image topics:", error);
39+
});
40+
dispatch(actions.openReviewDrawer());
41+
};
42+
43+
const getButtonText = (): string => {
44+
if (classification) {
45+
return t("claim:openReportButton");
46+
}
47+
return t("claim:reviewImageButton");
48+
};
49+
50+
return (
51+
<Box
52+
sx={{
53+
paddingBottom: "20px",
54+
display: "flex",
55+
flexDirection: "column",
56+
alignItems: "center",
57+
}}
58+
>
59+
<Box
60+
sx={{
61+
display: "flex",
62+
justifyContent: "center",
63+
alignItems: "center",
64+
width: "100%",
65+
}}
66+
>
67+
<ReviewedImage
68+
imageUrl={imageUrl}
69+
title={title}
70+
classification={classification}
71+
/>
72+
</Box>
73+
<Box
74+
sx={{ display: "flex", justifyContent: "center", marginTop: 2 }}
75+
>
76+
<AletheiaButton
77+
onClick={handleClickOnButton}
78+
style={{
79+
textTransform: "none",
80+
fontWeight: 600,
81+
padding: "10px 24px",
82+
}}
83+
>
84+
{getButtonText()}
85+
</AletheiaButton>
86+
</Box>
87+
</Box>
88+
);
89+
};
90+
91+
export default ClaimImageBody;

src/components/Claim/ClaimView.tsx

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Grid, Typography } from "@mui/material"
1+
import { Grid, Typography } from "@mui/material";
22
import React, { useEffect, useState } from "react";
33

44
import { ContentModelEnum, Roles, TargetModel } from "../../types/enums";
@@ -67,7 +67,8 @@ const ClaimView = ({ personality, claim, href, hideDescriptions }) => {
6767
)}
6868
<section>
6969
<ClaimInfo isImage={isImage} date={claim?.date} />
70-
<Grid container
70+
<Grid
71+
container
7172
style={{ paddingBottom: "15px" }}
7273
justifyContent="center"
7374
>
@@ -94,22 +95,37 @@ const ClaimView = ({ personality, claim, href, hideDescriptions }) => {
9495
}
9596
/>
9697
</Grid>
97-
<AffixButtonV2
98-
Children={
99-
<ToggleSection
100-
defaultValue={showHighlights}
101-
onChange={(e) => {
102-
setShowHighlights(e.target.value);
103-
}}
104-
labelTrue={t("claim:showHighlightsButton")}
105-
labelFalse={t("claim:hideHighlightsButton")}
106-
/>
107-
}
108-
/>
98+
{!isImage && (
99+
<AffixButtonV2
100+
Children={
101+
<ToggleSection
102+
defaultValue={showHighlights}
103+
onChange={(e) => {
104+
setShowHighlights(
105+
e.target.value
106+
);
107+
}}
108+
labelTrue={t(
109+
"claim:showHighlightsButton"
110+
)}
111+
labelFalse={t(
112+
"claim:hideHighlightsButton"
113+
)}
114+
/>
115+
}
116+
/>
117+
)}
109118
</Grid>
110119
{sources.length > 0 && (
111120
<>
112-
<Typography variant="h4" style={{ fontSize: 24, fontFamily: "initial", fontWeight: 700 }}>
121+
<Typography
122+
variant="h4"
123+
style={{
124+
fontSize: 24,
125+
fontFamily: "initial",
126+
fontWeight: 700,
127+
}}
128+
>
113129
{t("claim:sourceSectionTitle")}
114130
</Typography>
115131
<ClaimSourceList

src/components/ReviewedImage.tsx

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,35 @@
11
/* eslint-disable @next/next/no-img-element */
22
import React, { useEffect, useRef, useState } from "react";
33
import lottie from "lottie-web";
4-
import { generateLottie } from "../lottiefiles/generateLottie";
4+
import { generateLottie, LottieAnimation } from "../lottiefiles/generateLottie";
55
import { ClassificationEnum } from "../types/enums";
66

7+
interface ImageDimensions {
8+
width: number;
9+
height: number;
10+
}
11+
12+
const getImageMeta = (url: string): Promise<HTMLImageElement> =>
13+
new Promise((resolve, reject) => {
14+
const img = new Image();
15+
img.onload = () => resolve(img);
16+
img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
17+
img.src = url;
18+
});
19+
20+
const getDimensions = async (imageData: string): Promise<ImageDimensions> => {
21+
try {
22+
const image = await getImageMeta(imageData);
23+
const width = image.naturalWidth;
24+
const height = image.naturalHeight;
25+
26+
return { width, height };
27+
} catch (error) {
28+
console.error("Failed to load image dimensions:", error);
29+
return { width: 500, height: 500 };
30+
}
31+
};
32+
733
const ReviewedImage = ({
834
imageUrl,
935
title = "",
@@ -13,49 +39,12 @@ const ReviewedImage = ({
1339
title: string;
1440
classification?: keyof typeof ClassificationEnum;
1541
}) => {
16-
const [animation, setAnimation] = useState<any>(null);
17-
const container = useRef(null);
18-
const getImageMeta = (url) =>
19-
new Promise((resolve, reject) => {
20-
const img = new Image();
21-
img.onload = () => resolve(img);
22-
img.onerror = (err) => reject(err);
23-
img.src = url;
24-
});
42+
const [animation, setAnimation] = useState<LottieAnimation | null>(null);
43+
const container = useRef<HTMLDivElement>(null);
2544

26-
// get the natural width and height of the image or fit it to the window
27-
const getDimensions = async (imageData: any) => {
28-
try {
29-
const image = await getImageMeta(imageData);
30-
31-
// @ts-ignore
32-
let { naturalWidth: width, naturalHeight: height } = image;
33-
const windowHeight = window.innerHeight;
34-
// Responsive width calculation
35-
// Mobile: use 90% of viewport for better display
36-
// Desktop: use 1.5 ratio (was 2.25, now larger images)
37-
const isMobile = window.innerWidth < 768;
38-
const windowWidth = isMobile
39-
? window.innerWidth * 0.9
40-
: window.innerWidth / 1.5;
41-
42-
const aspectRatio = width / height;
43-
44-
if (height > windowHeight) {
45-
height = windowHeight;
46-
width = height * aspectRatio;
47-
}
48-
if (width > windowWidth) {
49-
width = windowWidth;
50-
height = width / aspectRatio;
51-
}
52-
return { width, height };
53-
} catch (error) {
54-
console.log(error);
55-
return { width: 500, height: 500 };
56-
}
57-
};
5845
useEffect(() => {
46+
if (!classification) return;
47+
5948
getDimensions(imageUrl).then(({ width, height }) => {
6049
const newAnimation = generateLottie(
6150
classification,
@@ -65,7 +54,7 @@ const ReviewedImage = ({
6554
);
6655
setAnimation(newAnimation);
6756
});
68-
}, []);
57+
}, [imageUrl, classification]);
6958

7059
useEffect(() => {
7160
if (classification) {
@@ -76,8 +65,7 @@ const ReviewedImage = ({
7665
autoplay: false,
7766
animationData: animation,
7867
rendererSettings: {
79-
preserveAspectRatio: "xMinYMin meet",
80-
viewBoxSize: "10 10",
68+
preserveAspectRatio: "xMidYMid meet",
8169
},
8270
});
8371
lottie.setSpeed(1.5);
@@ -105,9 +93,13 @@ const ReviewedImage = ({
10593
onMouseEnter={handleMouseEnter}
10694
onMouseLeave={handleMouseLeave}
10795
ref={container}
96+
aria-hidden="true"
10897
style={{
10998
maxWidth: "100%",
11099
maxHeight: "100vh",
100+
display: "flex",
101+
justifyContent: "center",
102+
alignItems: "center",
111103
}}
112104
/>
113105
)}
@@ -119,6 +111,8 @@ const ReviewedImage = ({
119111
style={{
120112
maxWidth: "100%",
121113
maxHeight: "100vh",
114+
display: "block",
115+
margin: "0 auto",
122116
}}
123117
/>
124118
)}

0 commit comments

Comments
 (0)