aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/Image/BackgroundImage.tsx35
-rw-r--r--src/components/Image/Image.tsx100
-rw-r--r--src/components/PollCard/PollCard.tsx29
-rw-r--r--src/containers/Home/Home.tsx7
4 files changed, 148 insertions, 23 deletions
diff --git a/src/components/Image/BackgroundImage.tsx b/src/components/Image/BackgroundImage.tsx
new file mode 100644
index 0000000..e749d10
--- /dev/null
+++ b/src/components/Image/BackgroundImage.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import Image from './Image';
+
+interface PropTypes {
+ src?: string;
+ alt?: string;
+}
+
+const useStyles = makeStyles({
+ root: {
+ position: 'absolute',
+ width: '100%',
+ height: '100%'
+ },
+ image: {
+ objectFit: 'cover',
+ pointerEvents: 'none',
+ width: '100%',
+ height: '100%'
+ }
+});
+
+const BackgroundImage: React.FC<PropTypes> = ({ src, alt }) => {
+ const classes = useStyles();
+
+ return (
+ <picture className={classes.root}>
+ <Image src={src} alt={alt} className={classes.image} />
+ </picture>
+ );
+};
+
+export default BackgroundImage;
+
diff --git a/src/components/Image/Image.tsx b/src/components/Image/Image.tsx
new file mode 100644
index 0000000..1898716
--- /dev/null
+++ b/src/components/Image/Image.tsx
@@ -0,0 +1,100 @@
+import React, { useState, useCallback, useMemo } from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import CircularProgress from '@material-ui/core/CircularProgress';
+import ErrorIcon from '@material-ui/icons/BrokenImage';
+import grey from '@material-ui/core/colors/grey';
+
+interface PropTypes {
+ src?: string;
+ alt?: string;
+ className?: string;
+ animationDuration?: number;
+ disableLoading?: boolean;
+}
+
+type Status = 'success' | 'loading' | 'error';
+
+const useStyles = makeStyles(theme => ({
+ container: {
+ width: '100%',
+ height: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
+ },
+ success: {
+ opacity: '100%',
+ filterBrightness: '100%',
+ filterSaturate: '100%'
+ },
+ loading: {
+ opacity: 0,
+ filterBrightness: 0,
+ filterSaturate: '20%',
+ position: 'absolute'
+ },
+ error: {
+ display: 'none'
+ },
+ errorIcon: {
+ color: grey[300],
+ width: theme.spacing(6),
+ height: theme.spacing(6)
+ }
+}));
+
+const Image: React.FC<PropTypes> = React.memo(({
+ src,
+ alt,
+ className,
+ animationDuration = 1000,
+ disableLoading = false
+}) => {
+ const classes = useStyles();
+ const [status, setStatus] = useState<Status>('loading');
+
+ const handleLoad = useCallback((): void => {
+ setStatus('success');
+ }, [setStatus]);
+
+ const handleError = useCallback((): void => {
+ setStatus('error');
+ }, [setStatus]);
+
+ const transition = useMemo(() => `
+ filterBrightness ${animationDuration * 0.75}ms cubic-bezier(0.4, 0.0, 0.2, 1),
+ filterSaturate ${animationDuration}ms cubic-bezier(0.4, 0.0, 0.2, 1),
+ opacity ${animationDuration / 2}ms cubic-bezier(0.4, 0.0, 0.2, 1)
+ `, [animationDuration]);
+
+ const image = (
+ <img
+ src={src}
+ alt={alt}
+ className={`${className} ${classes[status]}`}
+ style={{ transition }}
+ onLoad={handleLoad}
+ onError={handleError}
+ />
+ );
+
+ return (
+ <>
+ {src && image}
+ {
+ status !== 'success' && (
+ <div className={classes.container}>
+ {
+ (status === 'error' || !src)
+ ? <ErrorIcon className={classes.errorIcon} />
+ : (!disableLoading && <CircularProgress color="primary" />)
+ }
+ </div>
+ )
+ }
+ </>
+ );
+});
+
+export default Image;
+
diff --git a/src/components/PollCard/PollCard.tsx b/src/components/PollCard/PollCard.tsx
index 689e872..a4c2144 100644
--- a/src/components/PollCard/PollCard.tsx
+++ b/src/components/PollCard/PollCard.tsx
@@ -1,15 +1,12 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
-import {
- Card,
- CardActionArea,
- CardMedia
-} from '@material-ui/core/';
+import { Card, CardActionArea } from '@material-ui/core/';
import { Which, Poll } from 'which-types';
import { useSnackbar } from 'notistack';
import PercentageBar from './PercentageBar';
import UserStrip from '../UserStrip/UserStrip';
+import BackgroundImage from '../Image/BackgroundImage';
import { post } from '../../requests';
import { useAuth } from '../../hooks/useAuth';
@@ -27,12 +24,10 @@ const DATE_FORMAT = {
};
const useStyles = makeStyles(theme => ({
- images: {
+ media: {
+ display: 'flex',
height: theme.spacing(50)
},
- imagesBlock: {
- display: 'flex'
- },
rateLine: {
position: 'relative',
width: '100%',
@@ -101,19 +96,13 @@ const PollCard: React.FC<PropTypes> = ({ poll, setPoll }) => {
return (
<Card>
<UserStrip user={author} info={date} />
- <div className={classes.imagesBlock}>
- <CardActionArea onDoubleClick={handleLeft}>
- <CardMedia
- className={classes.images}
- image={left.url}
- />
+ <div className={classes.media}>
+ <CardActionArea onDoubleClick={handleLeft} className={classes.media}>
+ <BackgroundImage src={left.url} />
<PercentageBar value={leftPercentage} which="left" like={vote?.which === 'left'} />
</CardActionArea>
- <CardActionArea onDoubleClick={handleRight}>
- <CardMedia
- className={classes.images}
- image={right.url}
- />
+ <CardActionArea onDoubleClick={handleRight} className={classes.media}>
+ <BackgroundImage src={right.url} />
<PercentageBar value={rightPercentage} which="right" like={vote?.which === 'right'} />
</CardActionArea>
</div>
diff --git a/src/containers/Home/Home.tsx b/src/containers/Home/Home.tsx
index c0ca00e..072e2fa 100644
--- a/src/containers/Home/Home.tsx
+++ b/src/containers/Home/Home.tsx
@@ -13,10 +13,11 @@ import TrendingUpIcon from '@material-ui/icons/TrendingUp';
import { Rating } from '@material-ui/lab';
import { Feedback } from 'which-types';
-import { useAuth } from '../../hooks/useAuth';
-import { useFeedback } from '../../hooks/APIClient';
import ReviewCard from '../../components/ReviewCard/ReviewCard';
+import Image from '../../components/Image/Image';
import ReviewForm from './ReviewForm';
+import { useAuth } from '../../hooks/useAuth';
+import { useFeedback } from '../../hooks/APIClient';
const useStyles = makeStyles(theme => ({
root: {
@@ -109,7 +110,7 @@ const Home: React.FC = () => {
<Grid item xs={12} md={4}>
<Grid container direction="column" spacing={1} alignItems="center">
<Grid item>
- <img src={`${process.env.PUBLIC_URL}/which-logo-512.png`} alt="logo" className={classes.logo} />
+ <Image src={`${process.env.PUBLIC_URL}/which-logo-512.png`} alt="logo" className={classes.logo} />
</Grid>
<Grid item>
{rating !== 0 && <Rating value={rating} readOnly size="large" />}