show a preview of color/species on the homepage

Using the newly extracted OutfitPreview! I'm really happy with how this turned out :3

It also makes the pageload after clicking Start super smooth, no spinner! Thanks Apollo cache!!
This commit is contained in:
Emi Matchu 2020-07-22 22:07:45 -07:00
parent 9164d89240
commit 5a9d2f6566
3 changed files with 72 additions and 11 deletions

View file

@ -6,6 +6,7 @@ import { useHistory } from "react-router-dom";
import { useLazyQuery } from "@apollo/react-hooks"; import { useLazyQuery } from "@apollo/react-hooks";
import { Heading1, usePageTitle } from "./util"; import { Heading1, usePageTitle } from "./util";
import OutfitPreview from "./components/OutfitPreview";
import HomepageSplashImg from "../images/homepage-splash.png"; import HomepageSplashImg from "../images/homepage-splash.png";
import HomepageSplashImg2x from "../images/homepage-splash@2x.png"; import HomepageSplashImg2x from "../images/homepage-splash@2x.png";
@ -14,6 +15,8 @@ import SpeciesColorPicker from "./components/SpeciesColorPicker";
function HomePage() { function HomePage() {
usePageTitle("Dress to Impress"); usePageTitle("Dress to Impress");
const [previewState, setPreviewState] = React.useState(null);
return ( return (
<Flex <Flex
color="green.800" color="green.800"
@ -24,19 +27,31 @@ function HomePage() {
> >
<Box height="8" /> <Box height="8" />
<Box <Box
as="img"
src={HomepageSplashImg}
srcSet={`${HomepageSplashImg} 1x, ${HomepageSplashImg2x} 2x`}
alt=""
width="200px" width="200px"
height="200px" height="200px"
borderRadius="lg" borderRadius="lg"
boxShadow="md" boxShadow="md"
/> overflow="hidden"
>
<OutfitPreview
speciesId={previewState?.speciesId}
colorId={previewState?.colorId}
pose={previewState?.pose}
wornItemIds={[]}
placeholder={
<Box
as="img"
src={HomepageSplashImg}
srcSet={`${HomepageSplashImg} 1x, ${HomepageSplashImg2x} 2x`}
alt=""
/>
}
/>
</Box>
<Box height="4" /> <Box height="4" />
<Heading1>Dress to Impress</Heading1> <Heading1>Dress to Impress</Heading1>
<Box height="8" /> <Box height="8" />
<StartOutfitForm /> <StartOutfitForm onChange={setPreviewState} />
<Box height="4" /> <Box height="4" />
<Box fontStyle="italic" fontSize="sm"> <Box fontStyle="italic" fontSize="sm">
or or
@ -47,7 +62,7 @@ function HomePage() {
); );
} }
function StartOutfitForm() { function StartOutfitForm({ onChange }) {
const history = useHistory(); const history = useHistory();
const idealPose = React.useMemo( const idealPose = React.useMemo(
@ -89,6 +104,14 @@ function StartOutfitForm() {
setColorId(color.id); setColorId(color.id);
setIsValid(isValid); setIsValid(isValid);
setClosestPose(closestPose); setClosestPose(closestPose);
if (isValid) {
onChange({
speciesId: species.id,
colorId: color.id,
pose: closestPose,
});
}
}} }}
/> />
<Box width="4" /> <Box width="4" />

View file

@ -12,10 +12,16 @@ import useOutfitAppearance from "./useOutfitAppearance";
* fetches the appearance data for it, and preloads and renders the layers * fetches the appearance data for it, and preloads and renders the layers
* together. * together.
* *
* If the species/color/pose fields are null and a `placeholder` node is
* provided instead, we'll render the placeholder. And then, once those props
* become non-null, we'll keep showing the placeholder below the loading
* overlay until loading completes. (We use this on the homepage to show the
* beach splash until outfit data arrives!)
*
* TODO: There's some duplicate work happening in useOutfitAppearance and * TODO: There's some duplicate work happening in useOutfitAppearance and
* useOutfitState both getting appearance data on first load... * useOutfitState both getting appearance data on first load...
*/ */
function OutfitPreview({ speciesId, colorId, pose, wornItemIds }) { function OutfitPreview({ speciesId, colorId, pose, wornItemIds, placeholder }) {
const { loading, error, visibleLayers } = useOutfitAppearance({ const { loading, error, visibleLayers } = useOutfitAppearance({
speciesId, speciesId,
colorId, colorId,
@ -43,6 +49,7 @@ function OutfitPreview({ speciesId, colorId, pose, wornItemIds }) {
<OutfitLayers <OutfitLayers
loading={loading || loading2} loading={loading || loading2}
visibleLayers={loadedLayers} visibleLayers={loadedLayers}
placeholder={placeholder}
doAnimations doAnimations
/> />
); );
@ -52,9 +59,34 @@ function OutfitPreview({ speciesId, colorId, pose, wornItemIds }) {
* OutfitLayers is the raw UI component for rendering outfit layers. It's * OutfitLayers is the raw UI component for rendering outfit layers. It's
* used both in the main outfit preview, and in other minor UIs! * used both in the main outfit preview, and in other minor UIs!
*/ */
export function OutfitLayers({ loading, visibleLayers, doAnimations = false }) { export function OutfitLayers({
loading,
visibleLayers,
placeholder,
doAnimations = false,
}) {
const [isMounted, setIsMounted] = React.useState(false);
React.useEffect(() => {
setTimeout(() => setIsMounted(true), 0);
}, []);
console.log("opacity", isMounted && loading ? 1 : 0);
console.log("transition delay", loading ? "0.5s" : "0s");
return ( return (
<Box pos="relative" height="100%" width="100%"> <Box pos="relative" height="100%" width="100%">
{placeholder && (
<FullScreenCenter>
<Box
// We show the placeholder until there are visible layers, at which
// point we fade it out.
opacity={visibleLayers.length === 0 ? 1 : 0}
transition="opacity 0.2s"
>
{placeholder}
</Box>
</FullScreenCenter>
)}
<TransitionGroup enter={false} exit={doAnimations}> <TransitionGroup enter={false} exit={doAnimations}>
{visibleLayers.map((layer) => ( {visibleLayers.map((layer) => (
<CSSTransition <CSSTransition
@ -114,8 +146,13 @@ export function OutfitLayers({ loading, visibleLayers, doAnimations = false }) {
</TransitionGroup> </TransitionGroup>
<Box <Box
// This is similar to our Delay util component, but Delay disappears // This is similar to our Delay util component, but Delay disappears
// immediately on load, whereas we want this to fade out smoothly. // immediately on load, whereas we want this to fade out smoothly. We
opacity={loading ? 1 : 0} // also delay the fade-in by 0.5s, but don't delay the fade-out at all.
//
// We also use `isMounted` here to make sure it actually _fades_ in!
// (This starts the opacity at 0, then fires an immediate callback to
// set it to 1, triggering the transition.)
opacity={isMounted && loading ? 1 : 0}
transition={`opacity 0.2s ${loading ? "0.5s" : "0s"}`} transition={`opacity 0.2s ${loading ? "0.5s" : "0s"}`}
> >
<FullScreenCenter> <FullScreenCenter>

View file

@ -38,6 +38,7 @@ export default function useOutfitAppearance(outfitState) {
colorId, colorId,
pose, pose,
}, },
skip: speciesId == null || colorId == null || pose == null,
} }
); );