Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions dotcom-rendering/src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import type {
DCRSupportingContent,
} from '../../types/front';
import type { MainMedia } from '../../types/mainMedia';
import type { OnwardsSource } from '../../types/onwards';
import type { OnwardContainerType, OnwardsSource } from '../../types/onwards';
import { Avatar } from '../Avatar';
import { CardCommentCount } from '../CardCommentCount.importable';
import { CardHeadline, type ResponsiveFontSize } from '../CardHeadline';
Expand Down Expand Up @@ -120,7 +120,7 @@ export type Props = {
supportingContentPosition?: Position;
snapData?: DCRSnapType;
containerPalette?: DCRContainerPalette;
containerType?: DCRContainerType;
containerType?: DCRContainerType | OnwardContainerType;
showAge?: boolean;
discussionApiUrl: string;
discussionId?: string;
Expand Down Expand Up @@ -574,6 +574,8 @@ export const Card = ({
containerType === 'flexible/special' ||
containerType === 'flexible/general';

const isOnwardContainer = containerType === 'more-galleries';

const isSmallCard = containerType === 'scrollable/small';

const imageFixedSizeOptions = (): ImageFixedSizeOptions => {
Expand All @@ -589,7 +591,7 @@ export const Card = ({
};

const hideTrailTextUntil = () => {
if (isFlexibleContainer) {
if (isFlexibleContainer || (isOnwardContainer && isFlexSplash)) {
return undefined;
} else if (
imageSize === 'large' &&
Expand All @@ -602,6 +604,10 @@ export const Card = ({
}
};

const shouldShowTrailText = isOnwardContainer
? media?.type !== 'podcast' && isFlexSplash
: media?.type !== 'podcast';

/**
* Determines the gap of between card components based on card properties
* Order matters here as the logic is based on the card properties
Expand Down Expand Up @@ -1090,7 +1096,7 @@ export const Card = ({
</HeadlineWrapper>
)}

{!!trailText && media?.type !== 'podcast' && (
{!!trailText && shouldShowTrailText && (
<TrailText
trailText={trailText}
trailTextSize={trailTextSize}
Expand Down
20 changes: 11 additions & 9 deletions dotcom-rendering/src/components/Card/components/TrailText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ import { palette } from '../../../palette';

export type TrailTextSize = 'regular' | 'large';

const trailTextStyles = css`
display: flex;
flex-direction: column;
const trailTextStyles = (hideUntil?: 'tablet' | 'desktop' | 'mobile') => {
return css`
display: flex;
flex-direction: column;

${until.tablet} {
display: none;
}
`;
${hideUntil === 'mobile' ? until.mobile : until.tablet} {
display: none;
}
`;
};

const bottomPadding = css`
padding-bottom: ${space[2]}px;
Expand All @@ -44,7 +46,7 @@ type Props = {
/** Optionally overrides the trail text colour */
trailTextColour?: string;
/** Controls visibility of trail text on various breakpoints */
hideUntil?: 'tablet' | 'desktop';
hideUntil?: 'tablet' | 'desktop' | 'mobile';
/** Defaults to `true`. Adds padding to the bottom of the trail text */
padBottom?: boolean;
/** Adds padding to the top of the trail text */
Expand All @@ -62,7 +64,7 @@ export const TrailText = ({
const trailText = (
<div
css={[
trailTextStyles,
trailTextStyles(hideUntil),
css`
color: ${trailTextColour};
`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const DiscussionWeb = (
});
}, [authStatus, props.discussionApiUrl]);

if (!hydrated) return <Placeholder height={324} />;
if (!hydrated) return <Placeholder heights={new Map([['mobile', 324]])} />;

return (
<Discussion
Expand Down
176 changes: 176 additions & 0 deletions dotcom-rendering/src/components/FetchMoreGalleriesData.importable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { css } from '@emotion/react';
import { isNonNullable } from '@guardian/libs';
import { useEffect, useState } from 'react';
import { array, object, type Output, safeParse, string } from 'valibot';
import { decideFormat } from '../lib/articleFormat';
import { getDataLinkNameCard } from '../lib/getDataLinkName';
import { addDiscussionIds } from '../lib/useCommentCount';
import { palette } from '../palette';
import { type DCRFrontImage } from '../types/front';
import { type MainMedia } from '../types/mainMedia';
import type { OnwardsSource } from '../types/onwards';
import type { FETrailType, TrailType } from '../types/trails';
import { FETrailTypeSchema } from '../types/trails';
import { MoreGalleries } from './MoreGalleries';
import { Placeholder } from './Placeholder';

type Props = {
url: string;
limit: number; // Limit the number of items shown (the api often returns more)
onwardsSource: OnwardsSource;
discussionApiUrl: string;
absoluteServerTimes: boolean;
isAdFreeUser: boolean;
};

type MoreGalleriesResponse = Output<typeof MoreGalleriesResponseSchema>;

const MoreGalleriesResponseSchema = object({
trails: array(FETrailTypeSchema),
heading: string(),
});

const minHeight = css`
min-height: 300px;
`;

const getMedia = (galleryCount?: number): MainMedia | undefined => {
if (typeof galleryCount === 'number') {
return { type: 'Gallery', count: galleryCount.toString() };
}
return undefined;
};

const toGalleryTrail = (trail: FETrailType, index: number): TrailType => {
const format = decideFormat(trail.format);
const image: DCRFrontImage | undefined = trail.masterImage
? {
src: trail.masterImage,
altText: '',
}
: undefined;

return {
...trail,
image,
format,
dataLinkName: getDataLinkNameCard(format, '0', index),
mainMedia: getMedia(trail.galleryCount),
};
};

const buildTrails = (
trails: FETrailType[],
trailLimit: number,
isAdFreeUser: boolean,
): TrailType[] => {
return trails
.filter(
(trailType) =>
!(
trailType.branding?.brandingType?.name === 'paid-content' &&
isAdFreeUser
),
)
.slice(0, trailLimit)
.map(toGalleryTrail);
};

const fetchJson = async (ajaxUrl: string): Promise<MoreGalleriesResponse> => {
const fetchResponse = await fetch(ajaxUrl);
Comment thread Fixed
if (!fetchResponse.ok) {
throw new Error(`HTTP error! status: ${fetchResponse.status}`);
}
const responseJson: unknown = await fetchResponse.json();
const result = safeParse(MoreGalleriesResponseSchema, responseJson, {
abortEarly: true, // Avoid parsing the rest of the object after facing the first error
});
if (result.success) {
return result.output;
} else {
const errorMessages = result.issues
.map(
(issue) =>
`${issue.path?.map((p) => p.key).join('.') ?? 'root'}: ${
issue.message
}`,
)
.join('; ');
throw new Error(
`Failed to parse MoreGalleriesResponse: ${errorMessages}`,
);
}
};

export const FetchMoreGalleriesData = ({
url,
limit,
onwardsSource,
discussionApiUrl,
absoluteServerTimes,
isAdFreeUser,
}: Props) => {
const [data, setData] = useState<MoreGalleriesResponse | undefined>(
undefined,
);
const [error, setError] = useState<Error | undefined>(undefined);

useEffect(() => {
fetchJson(url)
.then((fetchedData) => {
setData(fetchedData);
setError(undefined);
})
.catch((err) => {
setError(
err instanceof Error ? err : new Error('Unknown error'),
);
setData(undefined);
});
}, [url]);

if (error) {
// Send the error to Sentry and then prevent the element from rendering
window.guardian.modules.sentry.reportError(error, 'more-galleries');
return undefined;
}

if (!data?.trails) {
return (
<Placeholder
heights={
new Map([
['mobile', 1020],
['mobileMedium', 1040],
['mobileLandscape', 1100],
['phablet', 1200],
['tablet', 700],
['desktop', 800],
['leftCol', 740],
['wide', 790],
])
}
shouldShimmer={false}
backgroundColor={palette('--onward-background')}
/>
);
}

addDiscussionIds(
data.trails
.map((trail) => trail.discussion?.discussionId)
.filter(isNonNullable),
);

return (
<div css={minHeight}>
<MoreGalleries
absoluteServerTimes={absoluteServerTimes}
trails={buildTrails(data.trails, limit, isAdFreeUser)}
discussionApiUrl={discussionApiUrl}
heading="More galleries"
onwardsSource={onwardsSource}
/>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const FetchOnwardsData = ({
if (!data?.trails) {
return (
<Placeholder
height={340} // best guess at typical height
heights={new Map([['mobile', 340]])} // best guess at typical height
shouldShimmer={false}
backgroundColor={palette('--article-background')}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type Props = {
format: ArticleFormat;
};

const Loading = () => <Placeholder height={172} />;
const Loading = () => <Placeholder heights={new Map([['mobile', 172]])} />;

export const GetCricketScoreboard = ({ matchUrl, format }: Props) => {
const options: SWRConfiguration = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Props = {
format: ArticleFormat;
};

const Loading = () => <Placeholder height={800} />;
const Loading = () => <Placeholder heights={new Map([['mobile', 800]])} />;

const cleanTeamCodes = ({
name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type Props = {
format: ArticleFormat;
};

const Loading = () => <Placeholder height={40} />;
const Loading = () => <Placeholder heights={new Map([['mobile', 40]])} />;

/**
* ## Why does this need to be an Island?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ export const InteractiveBlockComponent = ({
{!loaded && (
<>
<Placeholder // removed by HydrateInteractiveOnce
height={decideHeight(role)}
heights={new Map([['mobile', decideHeight(role)]])}
shouldShimmer={false}
/>
<a
Expand Down
4 changes: 2 additions & 2 deletions dotcom-rendering/src/components/LeftColumn.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const PartialRightBorder = () => {
}}
>
<Placeholder
height={500}
heights={new Map([['mobile', 500]])}
width={600}
shouldShimmer={false}
/>
Expand Down Expand Up @@ -58,7 +58,7 @@ export const RightBorder = () => {
}}
>
<Placeholder
height={500}
heights={new Map([['mobile', 500]])}
width={600}
shouldShimmer={false}
/>
Expand Down
Loading
Loading