Skip to content
Merged
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
15 changes: 8 additions & 7 deletions newIDE/app/src/AssetStore/ShopTiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import type { ExampleShortHeader } from '../Utils/GDevelopServices/Example';
import GridListTile from '@material-ui/core/GridListTile';
import { CorsAwareImage } from '../UI/CorsAwareImage';
import { ImageWithFallback } from '../UI/ImageWithFallback';
import { textEllipsisStyle } from '../UI/TextEllipsis';
import { Column, Line, Spacer } from '../UI/Grid';
import Text from '../UI/Text';
Expand Down Expand Up @@ -208,7 +209,7 @@ export const PublicAssetPackTile = ({
id={`asset-pack-${assetPack.tag.replace(/\s/g, '-')}`}
noOverflowParent
>
<CorsAwareImage
<ImageWithFallback
key={assetPack.name}
style={{
...styles.previewImage,
Expand Down Expand Up @@ -264,7 +265,7 @@ export const PrivateAssetPackTile = ({
>
<Column noMargin expand noOverflowParent>
<div style={styles.thumbnailContainer}>
<CorsAwareImage
<ImageWithFallback
key={assetPackListingData.name}
style={{
...styles.previewImage,
Expand Down Expand Up @@ -342,7 +343,7 @@ export const PromoBundleCard = ({
>
<ResponsiveLineStackLayout expand noMargin noResponsiveLandscape>
<div style={styles.promoImageContainer}>
<CorsAwareImage
<ImageWithFallback
key={bundleProductListingData.name}
style={{
...styles.previewImage,
Expand Down Expand Up @@ -487,7 +488,7 @@ export const PrivateGameTemplateTile = ({
noBorder
>
<Column noMargin expand noOverflowParent>
<CorsAwareImage
<ImageWithFallback
key={privateGameTemplateListingData.name}
style={{
...styles.previewImage,
Expand Down Expand Up @@ -548,7 +549,7 @@ export const CourseTile = ({
noBorder
>
<Column noMargin expand noOverflowParent>
<CorsAwareImage
<ImageWithFallback
key={courseListingData.name}
style={{
...styles.previewImage,
Expand Down Expand Up @@ -610,7 +611,7 @@ export const BundleTile = ({
noBorder
>
<Column noMargin expand noOverflowParent>
<CorsAwareImage
<ImageWithFallback
key={bundleListingData.name}
style={{
...styles.previewImage,
Expand Down Expand Up @@ -703,7 +704,7 @@ export const ExampleTile = ({
<Column noMargin expand noOverflowParent>
{exampleShortHeader ? (
thumbnailImgUrl ? (
<CorsAwareImage
<ImageWithFallback
key={exampleShortHeader.name}
style={{
...styles.previewImage,
Expand Down
11 changes: 11 additions & 0 deletions newIDE/app/src/UI/CustomSvgIcons/BrokenImage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import SvgIcon from '@material-ui/core/SvgIcon';

export default React.memo(props => (
<SvgIcon {...props} width="24" height="24" viewBox="0 0 24 24" fill="none">
<path
d="M21 5v6.59l-3-3.01-4 4.01-4-4-4 4-3-3.01V5c0-1.1.9-2 2-2h14c1.1 0 2 .9 2 2zm-3 6.42l3 3.01V19c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2v-6.58l3 2.99 4-4 4 4 4-3.99z"
fill="currentColor"
/>
</SvgIcon>
));
99 changes: 99 additions & 0 deletions newIDE/app/src/UI/ImageWithFallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// @flow
import * as React from 'react';
import { Trans } from '@lingui/macro';
import { CorsAwareImage } from './CorsAwareImage';
import BrokenImage from './CustomSvgIcons/BrokenImage';
import Text from './Text';
import GDevelopThemeContext from './Theme/GDevelopThemeContext';

type Props = {|
src: ?string,
alt: ?string,
style?: Object,
loading?: 'lazy',
|};

const styles = {
fallbackContainer: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
borderRadius: 8,
border: '1px solid',
boxSizing: 'border-box', // Take border in account for sizing to avoid cumulative layout shift.
},
fallbackIcon: {
width: '25%',
height: '25%',
opacity: 0.5,
},
};

/**
* An image that displays a placeholder icon when the source fails to load
* (broken/missing image), while keeping the same layout (size, border, ratio)
* to avoid any layout shift.
*/
export const ImageWithFallback = ({
src,
alt,
style,
loading,
...props
}: Props): React.MixedElement => {
const gdevelopTheme = React.useContext(GDevelopThemeContext);
const [hasError, setHasError] = React.useState(false);
const [isLoaded, setIsLoaded] = React.useState(false);

// Reset the loading/error state if the source changes (e.g. when navigating
// through a carousel, or when a tile is reused in a list).
React.useEffect(
() => {
setHasError(false);
setIsLoaded(false);
},
[src]
);

if (hasError || !src) {
return (
<div
style={{
...style,
...styles.fallbackContainer,
borderColor: gdevelopTheme.text.color.disabled,
}}
>
<BrokenImage
style={{
...styles.fallbackIcon,
color: gdevelopTheme.text.color.disabled,
}}
/>
<Text color="secondary" size="body-small" noMargin>
<Trans>Missing Content</Trans>
</Text>
</div>
);
}

return (
<CorsAwareImage
{...props}
src={src}
alt={alt}
// Keep the image invisible until it has successfully loaded, so the
// browser's native "broken image" glyph is never shown for a fraction of
// a second before the fallback appears.
style={{
...style,
opacity: isLoaded ? (style && style.opacity) || 1 : 0,
}}
loading={loading}
onLoad={() => setIsLoaded(true)}
onError={() => setHasError(true)}
/>
);
};
8 changes: 4 additions & 4 deletions newIDE/app/src/UI/ResponsiveMediaGallery.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Measure from 'react-measure';
import { makeStyles } from '@material-ui/core/styles';
import CardMedia from '@material-ui/core/CardMedia';
import Grid from '@material-ui/core/Grid';
import { CorsAwareImage } from './CorsAwareImage';
import { ImageWithFallback } from './ImageWithFallback';
import { Line } from './Grid';
import { shouldValidate } from './KeyboardShortcuts/InteractionKeys';
import { useResponsiveWindowSize } from './Responsive/ResponsiveWindowMeasurer';
Expand Down Expand Up @@ -192,7 +192,7 @@ const ResponsiveMediaGallery = ({
>
<CardMedia>
{kind === 'image' ? (
<CorsAwareImage
<ImageWithFallback
src={url}
style={{
...styles.mobileImageCarouselItem,
Expand Down Expand Up @@ -229,7 +229,7 @@ const ResponsiveMediaGallery = ({
return (
<ColumnStackLayout noMargin>
{selectedMedia.kind === 'image' ? (
<CorsAwareImage
<ImageWithFallback
style={styles.selectedMedia}
src={selectedMedia.url}
alt={altTextTemplate.replace(
Expand Down Expand Up @@ -264,7 +264,7 @@ const ResponsiveMediaGallery = ({
}}
>
{kind === 'image' ? (
<CorsAwareImage
<ImageWithFallback
src={url}
style={styles.imageCarouselItem}
alt={altTextTemplate.replace(
Expand Down
Loading