Skip to content

Commit 5dd7482

Browse files
committed
update gallery layout with new fetch more galleries component
1 parent e87f892 commit 5dd7482

File tree

3 files changed

+182
-0
lines changed

3 files changed

+182
-0
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import { css } from '@emotion/react';
2+
import { isNonNullable, isObject } from '@guardian/libs';
3+
import { useEffect, useState } from 'react';
4+
import { decideFormat } from '../lib/articleFormat';
5+
import { getDataLinkNameCard } from '../lib/getDataLinkName';
6+
import { addDiscussionIds } from '../lib/useCommentCount';
7+
import { palette } from '../palette';
8+
import { type DCRFrontImage } from '../types/front';
9+
import { type MainMedia } from '../types/mainMedia';
10+
import type { OnwardsSource } from '../types/onwards';
11+
import type { FETrailType, TrailType } from '../types/trails';
12+
import { MoreGalleries } from './MoreGalleries';
13+
import { Placeholder } from './Placeholder';
14+
15+
type Props = {
16+
url: string;
17+
limit: number; // Limit the number of items shown (the api often returns more)
18+
onwardsSource: OnwardsSource;
19+
discussionApiUrl: string;
20+
absoluteServerTimes: boolean;
21+
isAdFreeUser: boolean;
22+
};
23+
24+
type MoreGalleriesResponse = {
25+
heading: string;
26+
trails: FETrailType[];
27+
};
28+
29+
const minHeight = css`
30+
min-height: 300px;
31+
`;
32+
33+
const getMedia = (galleryCount?: number): MainMedia | undefined => {
34+
if (typeof galleryCount === 'number') {
35+
return { type: 'Gallery', count: galleryCount.toString() };
36+
}
37+
return undefined;
38+
};
39+
40+
const toGalleryTrail = (trail: FETrailType, index: number): TrailType => {
41+
const format = decideFormat(trail.format);
42+
const image: DCRFrontImage | undefined = trail.masterImage
43+
? {
44+
src: trail.masterImage,
45+
altText: '',
46+
}
47+
: undefined;
48+
49+
return {
50+
...trail,
51+
image,
52+
format,
53+
dataLinkName: getDataLinkNameCard(format, '0', index),
54+
mainMedia: getMedia(trail.galleryCount),
55+
};
56+
};
57+
58+
const buildTrails = (
59+
trails: FETrailType[],
60+
trailLimit: number,
61+
isAdFreeUser: boolean,
62+
): TrailType[] => {
63+
return trails
64+
.filter(
65+
(trailType) =>
66+
!(
67+
trailType.branding?.brandingType?.name === 'paid-content' &&
68+
isAdFreeUser
69+
),
70+
)
71+
.slice(0, trailLimit)
72+
.map(toGalleryTrail);
73+
};
74+
75+
const fetchJson = async (ajaxUrl: string): Promise<MoreGalleriesResponse> => {
76+
const fetchResponse = await fetch(ajaxUrl);
77+
if (!fetchResponse.ok) {
78+
throw new Error(`HTTP error! status: ${fetchResponse.status}`);
79+
}
80+
81+
const responseJson: unknown = await fetchResponse.json();
82+
83+
if (isObject(responseJson)) {
84+
// TODO: we need to properly validate this data in a future PR
85+
return responseJson as MoreGalleriesResponse;
86+
} else {
87+
throw new Error(
88+
'Failed to parse JSON MoreGalleriesResponse as an object',
89+
);
90+
}
91+
};
92+
93+
export const FetchMoreGalleriesData = ({
94+
url,
95+
limit,
96+
onwardsSource,
97+
discussionApiUrl,
98+
absoluteServerTimes,
99+
isAdFreeUser,
100+
}: Props) => {
101+
const [data, setData] = useState<MoreGalleriesResponse | undefined>(
102+
undefined,
103+
);
104+
const [error, setError] = useState<Error | undefined>(undefined);
105+
106+
useEffect(() => {
107+
fetchJson(url)
108+
.then((fetchedData) => {
109+
setData(fetchedData);
110+
setError(undefined);
111+
})
112+
.catch((err) => {
113+
setError(
114+
err instanceof Error ? err : new Error('Unknown error'),
115+
);
116+
setData(undefined);
117+
});
118+
}, [url]);
119+
120+
if (error) {
121+
// Send the error to Sentry and then prevent the element from rendering
122+
window.guardian.modules.sentry.reportError(error, 'more-galleries');
123+
return undefined;
124+
}
125+
126+
if (!data?.trails) {
127+
return (
128+
<Placeholder
129+
// Since the height of the 'MoreGalleries' component could vary quite a lot
130+
// on different breakpoints, we provide a map of heights for each breakpoint
131+
// in order to prevent layout shift
132+
heights={
133+
new Map([
134+
['mobile', 1020],
135+
['mobileMedium', 1040],
136+
['mobileLandscape', 1100],
137+
['phablet', 1200],
138+
['tablet', 700],
139+
['desktop', 800],
140+
['leftCol', 740],
141+
['wide', 790],
142+
])
143+
}
144+
shouldShimmer={false}
145+
backgroundColor={palette('--onward-background')}
146+
/>
147+
);
148+
}
149+
150+
addDiscussionIds(
151+
data.trails
152+
.map((trail) => trail.discussion?.discussionId)
153+
.filter(isNonNullable),
154+
);
155+
156+
return (
157+
<div css={minHeight}>
158+
<MoreGalleries
159+
absoluteServerTimes={absoluteServerTimes}
160+
trails={buildTrails(data.trails, limit, isAdFreeUser)}
161+
discussionApiUrl={discussionApiUrl}
162+
heading="More galleries"
163+
onwardsSource={onwardsSource}
164+
/>
165+
</div>
166+
);
167+
};

dotcom-rendering/src/layouts/GalleryLayout.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ArticleTitle } from '../components/ArticleTitle';
1818
import { Caption } from '../components/Caption';
1919
import { Carousel } from '../components/Carousel.importable';
2020
import { DiscussionLayout } from '../components/DiscussionLayout';
21+
import { FetchMoreGalleriesData } from '../components/FetchMoreGalleriesData.importable';
2122
import { Footer } from '../components/Footer';
2223
import { DesktopAdSlot, MobileAdSlot } from '../components/GalleryAdSlots';
2324
import { GalleryImage } from '../components/GalleryImage';
@@ -382,6 +383,18 @@ export const GalleryLayout = (props: WebProps | AppProps) => {
382383
frontendData.showBottomSocialButtons && isWeb
383384
}
384385
/>
386+
<Island priority="feature" defer={{ until: 'visible' }}>
387+
<FetchMoreGalleriesData
388+
url={`${gallery.frontendData.config.ajaxUrl}/gallery/most-viewed.json?dcr=true`}
389+
limit={5}
390+
onwardsSource={'more-galleries'}
391+
discussionApiUrl={discussionApiUrl}
392+
absoluteServerTimes={
393+
switches['absoluteServerTimes'] ?? false
394+
}
395+
isAdFreeUser={frontendData.isAdFreeUser}
396+
/>
397+
</Island>
385398
</main>
386399
{/* More galleries container */}
387400
{showMerchandisingHigh && (

dotcom-rendering/src/types/trails.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ interface BaseTrailType {
3030
discussionId?: string;
3131
};
3232
mainMedia?: MainMedia;
33+
trailText?: string;
34+
galleryCount?: number;
3335
}
3436

3537
export interface TrailType extends BaseTrailType {

0 commit comments

Comments
 (0)