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 { Heading1, usePageTitle } from "./util";
import OutfitPreview from "./components/OutfitPreview";
import HomepageSplashImg from "../images/homepage-splash.png";
import HomepageSplashImg2x from "../images/homepage-splash@2x.png";
@ -14,6 +15,8 @@ import SpeciesColorPicker from "./components/SpeciesColorPicker";
function HomePage() {
usePageTitle("Dress to Impress");
const [previewState, setPreviewState] = React.useState(null);
return (
<Flex
color="green.800"
@ -24,19 +27,31 @@ function HomePage() {
>
<Box height="8" />
<Box
as="img"
src={HomepageSplashImg}
srcSet={`${HomepageSplashImg} 1x, ${HomepageSplashImg2x} 2x`}
alt=""
width="200px"
height="200px"
borderRadius="lg"
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" />
<Heading1>Dress to Impress</Heading1>
<Box height="8" />
<StartOutfitForm />
<StartOutfitForm onChange={setPreviewState} />
<Box height="4" />
<Box fontStyle="italic" fontSize="sm">
or
@ -47,7 +62,7 @@ function HomePage() {
);
}
function StartOutfitForm() {
function StartOutfitForm({ onChange }) {
const history = useHistory();
const idealPose = React.useMemo(
@ -89,6 +104,14 @@ function StartOutfitForm() {
setColorId(color.id);
setIsValid(isValid);
setClosestPose(closestPose);
if (isValid) {
onChange({
speciesId: species.id,
colorId: color.id,
pose: closestPose,
});
}
}}
/>
<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
* 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
* 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({
speciesId,
colorId,
@ -43,6 +49,7 @@ function OutfitPreview({ speciesId, colorId, pose, wornItemIds }) {
<OutfitLayers
loading={loading || loading2}
visibleLayers={loadedLayers}
placeholder={placeholder}
doAnimations
/>
);
@ -52,9 +59,34 @@ function OutfitPreview({ speciesId, colorId, pose, wornItemIds }) {
* OutfitLayers is the raw UI component for rendering outfit layers. It's
* 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 (
<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}>
{visibleLayers.map((layer) => (
<CSSTransition
@ -114,8 +146,13 @@ export function OutfitLayers({ loading, visibleLayers, doAnimations = false }) {
</TransitionGroup>
<Box
// This is similar to our Delay util component, but Delay disappears
// immediately on load, whereas we want this to fade out smoothly.
opacity={loading ? 1 : 0}
// immediately on load, whereas we want this to fade out smoothly. We
// 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"}`}
>
<FullScreenCenter>

View file

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