Skip to content

Commit

Permalink
fix(project): dynamic blur
Browse files Browse the repository at this point in the history
  • Loading branch information
royschut committed May 7, 2021
1 parent c980657 commit f2ce8dc
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 70 deletions.
1 change: 1 addition & 0 deletions src/components/DynamicBlur/DynamicBlur.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
box-sizing: border-box;
filter: blur(30px);
z-index: -1;
opacity: 0;
}
76 changes: 35 additions & 41 deletions src/components/DynamicBlur/DynamicBlur.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,62 @@
import React, { memo, useEffect, useRef } from 'react';
import React, { memo, useEffect, useRef, useState } from 'react';

import { debounce } from '../../utils/common';

import styles from './DynamicBlur.module.scss';

type ImgState = {
current: 'first' | 'second' | 'none';
srcFirst: string;
srcSecond: string;
};

const defaultImgState: ImgState = {
current: 'none',
srcFirst: '',
srcSecond: '',
};

type Props = {
url: string;
transitionTime?: number;
debounceTime?: number;
};

const DynamicBlur: React.FC<Props> = ({ url, transitionTime = 1 }: Props) => {
const image = useRef(defaultImgState);
const loadImgRef = useRef(debounce((url: string, imgState: ImgState) => loadImage(url, imgState), 350));

const getImgState = image.current;

const setImgState = (imgState: ImgState) => {
image.current = imgState;
};
const DynamicBlur = ({ url, transitionTime = 1, debounceTime = 350 }: Props): JSX.Element => {
const [currentUrl, setCurrentUrl] = useState<string>();
const [currentImg, setCurrentImg] = useState<number>();
const firstImage = useRef<HTMLDivElement>() as React.MutableRefObject<HTMLDivElement>;
const secondImage = useRef<HTMLDivElement>() as React.MutableRefObject<HTMLDivElement>;
const loadImgDebounced = useRef(
debounce((url: string, currentImg: number) => loadImage(url, currentImg), debounceTime),
);

const loadImage = (url: string, imgState: ImgState) => {
const loadImage = (url: string, currentImg: number) => {
const img = document.createElement('img');
img.onload = () => {
setImgState({
current: imgState.current === 'first' ? 'second' : 'first',
srcFirst: imgState.current === 'first' ? imgState.srcFirst : url,
srcSecond: imgState.current === 'second' ? imgState.srcSecond : url,
});
if (!firstImage.current || !secondImage.current) {
return;
}
if (currentImg !== 1) {
firstImage.current.style.backgroundImage = `url('${url}')`;
firstImage.current.style.opacity = '0.3';
secondImage.current.style.opacity = '0';
return setCurrentImg(1);
} else {
secondImage.current.style.backgroundImage = `url('${url}')`;
firstImage.current.style.opacity = '0';
secondImage.current.style.opacity = '0.3';
return setCurrentImg(2);
}
};
if (url) img.src = url;
img.src = url;
};

useEffect(() => {
if (url !== getImgState.srcFirst && url !== getImgState.srcSecond) loadImgRef.current(url, getImgState);
}, [url, getImgState]);
if (url && url !== currentUrl) {
setCurrentUrl(url);
loadImgDebounced.current(url, currentImg);
}
}, [url, currentUrl, currentImg]);

return (
<React.Fragment>
<div
style={{
background: `url('${getImgState.srcFirst}')`,
opacity: getImgState.current === 'first' ? 0.3 : 0,
transition: `opacity ${transitionTime}s ease-in-out`,
}}
ref={firstImage}
style={{ transition: `opacity ${transitionTime}s ease-in-out` }}
className={styles.BlurBackground}
/>
<div
style={{
background: `url('${getImgState.srcSecond}')`,
opacity: getImgState.current === 'second' ? 0.3 : 0,
transition: `opacity ${transitionTime}s ease-in-out`,
}}
ref={secondImage}
style={{ transition: `opacity ${transitionTime}s ease-in-out` }}
className={styles.BlurBackground}
/>
</React.Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ exports[`<DynamicBlur> renders and matches snapshot 1`] = `
<div>
<div
class="BlurBackground"
style="background: url(); opacity: 0; transition: opacity 1s ease-in-out;"
style="transition: opacity 1s ease-in-out;"
/>
<div
class="BlurBackground"
style="background: url(); opacity: 0; transition: opacity 1s ease-in-out;"
style="transition: opacity 1s ease-in-out;"
/>
</div>
`;
2 changes: 1 addition & 1 deletion src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const Layout: FC<LayoutProps> = ({ children }) => {

return (
<div className={styles.layout}>
{hasDynamicBlur && blurImage && <DynamicBlur url={blurImage} />}
{hasDynamicBlur && blurImage && <DynamicBlur url={blurImage} transitionTime={1} debounceTime={350} />}
<Header
onMenuButtonClick={() => setSideBarOpen(true)}
playlistMenuItems={playlistMenuItems}
Expand Down
3 changes: 2 additions & 1 deletion src/container/Shelf/Shelf.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import type { PlaylistItem } from 'types/playlist';

import usePlaylist, { UsePlaylistResult } from '../../hooks/usePlaylist';
import ShelfComponent from '../../components/Shelf/Shelf';
import type { PlaylistItem } from 'types/playlist';

type ShelfProps = {
playlistId: string;
Expand Down
6 changes: 3 additions & 3 deletions src/providers/uiStateProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { createContext, FunctionComponent, ReactNode, useState } from 'react';
import React, { createContext, FunctionComponent, ReactNode, useCallback, useState } from 'react';

export type UpdateBlurImage = (image: string) => void;
export type BlurImage = string;
Expand All @@ -19,8 +19,8 @@ export type ProviderProps = {
};

const UIStateProvider: FunctionComponent<ProviderProps> = ({ children }) => {
const [blurImage, setBlurImage] = useState<BlurImage>(() => defaultContext.blurImage);
const updateBlurImage: UpdateBlurImage = (image: BlurImage) => setBlurImage(image);
const [blurImage, setBlurImage] = useState<BlurImage>(defaultContext.blurImage);
const updateBlurImage: UpdateBlurImage = useCallback((image: BlurImage) => setBlurImage(image), []);

return <UIStateContext.Provider value={{ blurImage, updateBlurImage }}>{children}</UIStateContext.Provider>;
};
Expand Down
23 changes: 12 additions & 11 deletions src/screens/Home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { Config, Content } from 'types/Config';
import type { PlaylistItem } from 'types/playlist';

import { featuredTileBreakpoints, tileBreakpoints } from '../../components/Shelf/Shelf';
import { UIStateContext, UpdateBlurImage } from '../../providers/uiStateProvider';
import { UIStateContext } from '../../providers/uiStateProvider';
import Shelf from '../../container/Shelf/Shelf';
import { ConfigContext } from '../../providers/configProvider';
import type { UsePlaylistResult } from '../../hooks/usePlaylist';
Expand All @@ -26,11 +26,11 @@ type rowData = {
type ItemData = {
content: Content[];
onCardClick: (playlistItem: PlaylistItem) => void;
updateBlurImage: UpdateBlurImage;
onCardHover: (playlistItem: PlaylistItem) => void;
};

const renderRow = ({ index, key, style, itemData }: rowData) => {
const { content, onCardClick, updateBlurImage } = itemData;
const { content, onCardClick, onCardHover } = itemData;
const contentItem: Content = content[index];
if (!contentItem) return null;

Expand All @@ -40,33 +40,34 @@ const renderRow = ({ index, key, style, itemData }: rowData) => {
key={contentItem.playlistId}
playlistId={contentItem.playlistId}
onCardClick={onCardClick}
onCardHover={(playlistItem: PlaylistItem) => updateBlurImage(playlistItem.image)}
onCardHover={onCardHover}
featured={contentItem.featured === true}
/>
</div>
);
};

const createItemData = memoize((content, onCardClick, updateBlurImage) => ({
const createItemData = memoize((content, onCardClick, onCardHover) => ({
content,
onCardClick,
updateBlurImage,
onCardHover,
}));

const Home = (): JSX.Element => {
const history = useHistory();
const config: Config = useContext(ConfigContext);
const { updateBlurImage } = useContext(UIStateContext);
const { blurImage, updateBlurImage } = useContext(UIStateContext);
const breakpoint = useBreakpoint();
const listRef = useRef<List>() as React.MutableRefObject<List>;
const content: Content[] = config ? config.content : [];

const usePlaylistArg: string | undefined = content.length ? content[0]?.playlistId : undefined;
const { data: firstPlaylist }: UsePlaylistResult = usePlaylist(usePlaylistArg || '');

const onCardClick = (playlistItem: PlaylistItem) => playlistItem && history.push(`/play/${playlistItem.mediaid}`);
const onCardClick = (playlistItem: PlaylistItem) => history.push(`/play/${playlistItem.mediaid}`);
const onCardHover = (playlistItem: PlaylistItem) => updateBlurImage(playlistItem.image);

const itemData: ItemData = createItemData(content, onCardClick, updateBlurImage);
const itemData: ItemData = createItemData(content, onCardClick, onCardHover);

const calculateHeight = (index: number): number => {
const item = content[index];
Expand All @@ -85,8 +86,8 @@ const Home = (): JSX.Element => {
};

useEffect(() => {
if (firstPlaylist?.playlist[0]?.image) updateBlurImage(firstPlaylist.playlist[0].image);
}, [firstPlaylist, updateBlurImage]);
if (firstPlaylist?.playlist.length && !blurImage) updateBlurImage(firstPlaylist.playlist[0].image);
}, [firstPlaylist, blurImage, updateBlurImage]);

return (
<div className={styles.home}>
Expand Down
33 changes: 22 additions & 11 deletions src/screens/Playlist/Playlist.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React, { useState } from 'react';
import type { RouteComponentProps } from 'react-router-dom';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { RouteComponentProps, useHistory } from 'react-router-dom';
import type { GridCellProps } from 'react-virtualized';
import type { PlaylistItem } from 'types/playlist';

import VirtualizedGrid from '../../components/VirtualizedGrid/VirtualizedGrid';
import usePlaylist from '../../hooks/usePlaylist';
import { getCategoriesFromPlaylist, filterPlaylistCategory, chunk } from '../../utils/collection';
import Card from '../../components/Card/Card';
import Dropdown from '../../components/Filter/Filter';
import useBreakpoint, { Breakpoint } from '../../hooks/useBreakpoint';
import { UIStateContext } from '../../providers/uiStateProvider';

import styles from './Playlist.module.scss';

Expand All @@ -28,33 +30,42 @@ function Playlist({
params: { id },
},
}: RouteComponentProps<PlaylistRouteParams>) {
const { isLoading, error, data: { title, playlist } = {} } = usePlaylist(id);
const history = useHistory();
const { updateBlurImage } = useContext(UIStateContext);
const { isLoading, error, data: { title, playlist } = { title: '', playlist: [] } } = usePlaylist(id);
const [filter, setFilter] = useState<string>('');
const breakpoint: Breakpoint = useBreakpoint();

if (isLoading) return <p>Loading...</p>;
const categories = getCategoriesFromPlaylist(playlist);
const filteredPlaylist = useMemo(() => filterPlaylistCategory(playlist, filter), [playlist, filter]);
const playlistRows = chunk<PlaylistItem>(filteredPlaylist, cols[breakpoint]);

if (error || !playlist) return <p>No playlist found...</p>;
const onCardClick = (playlistItem: PlaylistItem) => history.push(`/play/${playlistItem.mediaid}`);
const onCardHover = (playlistItem: PlaylistItem) => updateBlurImage(playlistItem.image);

const categories = getCategoriesFromPlaylist(playlist);
const filteredPlaylist = filterPlaylistCategory(playlist, filter);
useEffect(() => {
if (filteredPlaylist.length) updateBlurImage(filteredPlaylist[0].image);
}, [filter, filteredPlaylist, updateBlurImage]);

const playlistRows = chunk(filteredPlaylist, cols[breakpoint]);
if (isLoading) return <p>Loading...</p>;
if (error || !playlist) return <p>No playlist found...</p>;

const cellRenderer = ({ columnIndex, key, rowIndex, style }: GridCellProps) => {
if (!playlistRows[rowIndex][columnIndex]) return;

const { mediaid: mediaId, title, duration, image, seriesId } = playlistRows[rowIndex][columnIndex];
const playlistItem: PlaylistItem = playlistRows[rowIndex][columnIndex];
const { mediaid, title, duration, image, seriesId } = playlistItem;

return (
<div className={styles.wrapper} style={style} key={key}>
<Card
key={mediaId}
key={mediaid}
title={title}
duration={duration}
posterSource={image}
seriesId={seriesId}
onClick={() => ''}
onClick={() => onCardClick(playlistItem)}
onHover={() => onCardHover(playlistItem)}
/>
</div>
);
Expand Down

0 comments on commit f2ce8dc

Please sign in to comment.