Add HTML5Badge to outfit page
This commit is contained in:
parent
bf85cef922
commit
0c80491f99
4 changed files with 179 additions and 135 deletions
|
@ -38,6 +38,7 @@ import { Link, useParams } from "react-router-dom";
|
|||
|
||||
import ItemPageLayout, { SubtleSkeleton } from "./ItemPageLayout";
|
||||
import { Delay, logAndCapture, usePageTitle } from "./util";
|
||||
import HTML5Badge from "./components/HTML5Badge";
|
||||
import {
|
||||
itemAppearanceFragment,
|
||||
petAppearanceFragment,
|
||||
|
@ -895,127 +896,6 @@ function ExpandOnGroupHover({ children, ...props }) {
|
|||
);
|
||||
}
|
||||
|
||||
function HTML5Badge({ usesHTML5, isLoading }) {
|
||||
const greenBackground = useColorModeValue("green.100", "green.900");
|
||||
const greenBorderColor = useColorModeValue("green.600", "green.500");
|
||||
const greenTextColor = useColorModeValue("green.700", "white");
|
||||
|
||||
const yellowBackground = useColorModeValue("yellow.100", "yellow.900");
|
||||
const yellowBorderColor = useColorModeValue("yellow.600", "yellow.500");
|
||||
const yellowTextColor = useColorModeValue("yellow.700", "white");
|
||||
|
||||
// `delayedUsesHTML5` stores the last known value of `usesHTML5`, when
|
||||
// `isLoading` was `false`. This enables us to keep showing the badge, even
|
||||
// when loading a new appearance - because it's unlikely the badge will
|
||||
// change between different appearances for the same item, and the flicker is
|
||||
// annoying!
|
||||
const [delayedUsesHTML5, setDelayedUsesHTML5] = React.useState(null);
|
||||
React.useEffect(() => {
|
||||
if (!isLoading) {
|
||||
setDelayedUsesHTML5(usesHTML5);
|
||||
}
|
||||
}, [usesHTML5, isLoading]);
|
||||
|
||||
if (delayedUsesHTML5 === true) {
|
||||
return (
|
||||
<HTML5BadgeLayout
|
||||
backgroundColor={greenBackground}
|
||||
borderColor={greenBorderColor}
|
||||
color={greenTextColor}
|
||||
aria-label="HTML5 supported!"
|
||||
tooltipLabel="This item is converted to HTML5, and ready to use on Neopets.com!"
|
||||
>
|
||||
<CheckCircleIcon fontSize="xs" />
|
||||
<Icon
|
||||
viewBox="0 0 36 36"
|
||||
fontSize="xl"
|
||||
// Visual re-balancing, there's too much visual right-padding here!
|
||||
marginRight="-1"
|
||||
>
|
||||
{/* From Twemoji Keycap 5 */}
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M16.389 14.489c.744-.155 1.551-.31 2.326-.31 3.752 0 6.418 2.977 6.418 6.604 0 5.178-2.851 8.589-8.216 8.589-2.201 0-6.821-1.427-6.821-4.155 0-1.147.961-2.107 2.108-2.107 1.24 0 2.729 1.984 4.806 1.984 2.17 0 3.288-2.109 3.288-4.062 0-1.86-1.055-3.131-2.977-3.131-1.799 0-2.078 1.023-3.659 1.023-1.209 0-1.829-.93-1.829-1.457 0-.403.062-.713.093-1.054l.774-6.544c.341-2.418.93-2.945 2.418-2.945h7.472c1.428 0 2.264.837 2.264 1.953 0 2.14-1.611 2.326-2.17 2.326h-5.829l-.466 3.286z"
|
||||
/>
|
||||
</Icon>
|
||||
</HTML5BadgeLayout>
|
||||
);
|
||||
} else if (delayedUsesHTML5 === false) {
|
||||
return (
|
||||
<HTML5BadgeLayout
|
||||
backgroundColor={yellowBackground}
|
||||
borderColor={yellowBorderColor}
|
||||
color={yellowTextColor}
|
||||
aria-label="HTML5 not supported"
|
||||
tooltipLabel={
|
||||
<>
|
||||
This item isn't converted to HTML5 yet, so it might not appear in
|
||||
Neopets.com customization yet. Once it's ready, it could look a bit
|
||||
different than our temporary preview here. It might even be
|
||||
animated!
|
||||
</>
|
||||
}
|
||||
>
|
||||
<WarningTwoIcon fontSize="xs" marginRight="1" />
|
||||
<Icon viewBox="0 0 36 36" fontSize="xl">
|
||||
{/* From Twemoji Keycap 5 */}
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M16.389 14.489c.744-.155 1.551-.31 2.326-.31 3.752 0 6.418 2.977 6.418 6.604 0 5.178-2.851 8.589-8.216 8.589-2.201 0-6.821-1.427-6.821-4.155 0-1.147.961-2.107 2.108-2.107 1.24 0 2.729 1.984 4.806 1.984 2.17 0 3.288-2.109 3.288-4.062 0-1.86-1.055-3.131-2.977-3.131-1.799 0-2.078 1.023-3.659 1.023-1.209 0-1.829-.93-1.829-1.457 0-.403.062-.713.093-1.054l.774-6.544c.341-2.418.93-2.945 2.418-2.945h7.472c1.428 0 2.264.837 2.264 1.953 0 2.14-1.611 2.326-2.17 2.326h-5.829l-.466 3.286z"
|
||||
/>
|
||||
|
||||
{/* From Twemoji Not Allowed */}
|
||||
<path
|
||||
fill="#DD2E44"
|
||||
opacity="0.75"
|
||||
d="M18 0C8.059 0 0 8.059 0 18s8.059 18 18 18 18-8.059 18-18S27.941 0 18 0zm13 18c0 2.565-.753 4.95-2.035 6.965L11.036 7.036C13.05 5.753 15.435 5 18 5c7.18 0 13 5.821 13 13zM5 18c0-2.565.753-4.95 2.036-6.964l17.929 17.929C22.95 30.247 20.565 31 18 31c-7.179 0-13-5.82-13-13z"
|
||||
/>
|
||||
</Icon>
|
||||
</HTML5BadgeLayout>
|
||||
);
|
||||
} else {
|
||||
// If no `usesHTML5` value has been provided yet, we're empty for now!
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function HTML5BadgeLayout({ children, tooltipLabel, ...props }) {
|
||||
const [isHovered, setIsHovered] = React.useState(false);
|
||||
const [isFocused, setIsFocused] = React.useState(false);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
textAlign="center"
|
||||
fontSize="xs"
|
||||
placement="bottom-start"
|
||||
label={tooltipLabel}
|
||||
// HACK: Chakra tooltips seem inconsistent about staying open when focus
|
||||
// comes from touch events. But I really want this one to work on
|
||||
// mobile!
|
||||
isOpen={isHovered || isFocused}
|
||||
>
|
||||
<Flex
|
||||
align="center"
|
||||
border="1px solid"
|
||||
borderRadius="md"
|
||||
boxShadow="md"
|
||||
paddingX="2"
|
||||
paddingY="1"
|
||||
transition="all 0.2s"
|
||||
tabIndex="0"
|
||||
_focus={{ outline: "none", boxShadow: "outline" }}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
function PlayPauseButton({ isPaused, onClick }) {
|
||||
return (
|
||||
<IconButton
|
||||
|
|
|
@ -27,6 +27,7 @@ import SpeciesColorPicker from "../components/SpeciesColorPicker";
|
|||
import { loadImage, useLocalStorage } from "../util";
|
||||
import useCurrentUser from "../components/useCurrentUser";
|
||||
import useOutfitAppearance from "../components/useOutfitAppearance";
|
||||
import HTML5Badge from "../components/HTML5Badge";
|
||||
|
||||
/**
|
||||
* OutfitControls is the set of controls layered over the outfit preview, to
|
||||
|
@ -36,6 +37,7 @@ function OutfitControls({
|
|||
outfitState,
|
||||
dispatchToOutfit,
|
||||
showAnimationControls,
|
||||
appearance,
|
||||
}) {
|
||||
const [focusIsLocked, setFocusIsLocked] = React.useState(false);
|
||||
const onLockFocus = React.useCallback(() => setFocusIsLocked(true), [
|
||||
|
@ -84,6 +86,11 @@ function OutfitControls({
|
|||
}
|
||||
};
|
||||
|
||||
const itemLayers = appearance.itemAppearances.map((a) => a.layers).flat();
|
||||
const usesHTML5 = itemLayers.every(
|
||||
(l) => l.svgUrl || l.canvasMovieLibraryUrl
|
||||
);
|
||||
|
||||
return (
|
||||
<ClassNames>
|
||||
{({ css, cx }) => (
|
||||
|
@ -173,7 +180,32 @@ function OutfitControls({
|
|||
* We try to center the species/color picker, but the left spacer will
|
||||
* shrink more than the pose picker container if we run out of space!
|
||||
*/}
|
||||
<Box flex="1 1 0" />
|
||||
<Flex
|
||||
flex="1 1 0"
|
||||
paddingRight="2"
|
||||
align="center"
|
||||
justify="center"
|
||||
>
|
||||
<HTML5Badge
|
||||
usesHTML5={usesHTML5}
|
||||
isLoading={appearance.loading}
|
||||
tooltipLabel={
|
||||
usesHTML5 ? (
|
||||
<>
|
||||
This outfit is converted to HTML5, and ready to use on
|
||||
Neopets.com!
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
This outfit isn't converted to HTML5 yet, so it might
|
||||
not appear in Neopets.com customization yet. Once it's
|
||||
ready, it could look a bit different than our temporary
|
||||
preview here. It might even be animated!
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
<Box flex="0 0 auto">
|
||||
<DarkMode>
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ import OutfitThumbnail, {
|
|||
outfitThumbnailFragment,
|
||||
getOutfitThumbnailRenderSize,
|
||||
} from "../components/OutfitThumbnail";
|
||||
import OutfitPreview from "../components/OutfitPreview";
|
||||
import OutfitPreview, { useOutfitPreview } from "../components/OutfitPreview";
|
||||
import { loadable } from "../util";
|
||||
|
||||
const OutfitControls = loadable(() => import("./OutfitControls"));
|
||||
|
@ -21,27 +21,28 @@ function WardrobePreviewAndControls({
|
|||
// show the play/pause button.
|
||||
const [hasAnimations, setHasAnimations] = React.useState(false);
|
||||
|
||||
const { appearance, preview } = useOutfitPreview({
|
||||
isLoading: isLoading,
|
||||
speciesId: outfitState.speciesId,
|
||||
colorId: outfitState.colorId,
|
||||
pose: outfitState.pose,
|
||||
appearanceId: outfitState.appearanceId,
|
||||
wornItemIds: outfitState.wornItemIds,
|
||||
onChangeHasAnimations: setHasAnimations,
|
||||
backdrop: <OutfitThumbnailIfCached outfitId={outfitState.id} />,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box position="absolute" top="0" bottom="0" left="0" right="0">
|
||||
<DarkMode>
|
||||
<OutfitPreview
|
||||
isLoading={isLoading}
|
||||
speciesId={outfitState.speciesId}
|
||||
colorId={outfitState.colorId}
|
||||
pose={outfitState.pose}
|
||||
appearanceId={outfitState.appearanceId}
|
||||
wornItemIds={outfitState.wornItemIds}
|
||||
onChangeHasAnimations={setHasAnimations}
|
||||
backdrop={<OutfitThumbnailIfCached outfitId={outfitState.id} />}
|
||||
/>
|
||||
</DarkMode>
|
||||
<DarkMode>{preview}</DarkMode>
|
||||
</Box>
|
||||
<Box position="absolute" top="0" bottom="0" left="0" right="0">
|
||||
<OutfitControls
|
||||
outfitState={outfitState}
|
||||
dispatchToOutfit={dispatchToOutfit}
|
||||
showAnimationControls={hasAnimations}
|
||||
appearance={appearance}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
|
|
131
src/app/components/HTML5Badge.js
Normal file
131
src/app/components/HTML5Badge.js
Normal file
|
@ -0,0 +1,131 @@
|
|||
import React from "react";
|
||||
import { Tooltip, useColorModeValue, Flex, Icon } from "@chakra-ui/react";
|
||||
import { CheckCircleIcon, WarningTwoIcon } from "@chakra-ui/icons";
|
||||
|
||||
function HTML5Badge({ usesHTML5, isLoading, tooltipLabel }) {
|
||||
const greenBackground = useColorModeValue("green.100", "green.900");
|
||||
const greenBorderColor = useColorModeValue("green.600", "green.500");
|
||||
const greenTextColor = useColorModeValue("green.700", "white");
|
||||
|
||||
const yellowBackground = useColorModeValue("yellow.100", "yellow.900");
|
||||
const yellowBorderColor = useColorModeValue("yellow.600", "yellow.500");
|
||||
const yellowTextColor = useColorModeValue("yellow.700", "white");
|
||||
|
||||
// `delayedUsesHTML5` stores the last known value of `usesHTML5`, when
|
||||
// `isLoading` was `false`. This enables us to keep showing the badge, even
|
||||
// when loading a new appearance - because it's unlikely the badge will
|
||||
// change between different appearances for the same item, and the flicker is
|
||||
// annoying!
|
||||
const [delayedUsesHTML5, setDelayedUsesHTML5] = React.useState(null);
|
||||
React.useEffect(() => {
|
||||
if (!isLoading) {
|
||||
setDelayedUsesHTML5(usesHTML5);
|
||||
}
|
||||
}, [usesHTML5, isLoading]);
|
||||
|
||||
if (delayedUsesHTML5 === true) {
|
||||
return (
|
||||
<HTML5BadgeLayout
|
||||
backgroundColor={greenBackground}
|
||||
borderColor={greenBorderColor}
|
||||
color={greenTextColor}
|
||||
aria-label="HTML5 supported!"
|
||||
tooltipLabel={
|
||||
tooltipLabel ||
|
||||
"This item is converted to HTML5, and ready to use on Neopets.com!"
|
||||
}
|
||||
>
|
||||
<CheckCircleIcon fontSize="xs" />
|
||||
<Icon
|
||||
viewBox="0 0 36 36"
|
||||
fontSize="xl"
|
||||
// Visual re-balancing, there's too much visual right-padding here!
|
||||
marginRight="-1"
|
||||
>
|
||||
{/* From Twemoji Keycap 5 */}
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M16.389 14.489c.744-.155 1.551-.31 2.326-.31 3.752 0 6.418 2.977 6.418 6.604 0 5.178-2.851 8.589-8.216 8.589-2.201 0-6.821-1.427-6.821-4.155 0-1.147.961-2.107 2.108-2.107 1.24 0 2.729 1.984 4.806 1.984 2.17 0 3.288-2.109 3.288-4.062 0-1.86-1.055-3.131-2.977-3.131-1.799 0-2.078 1.023-3.659 1.023-1.209 0-1.829-.93-1.829-1.457 0-.403.062-.713.093-1.054l.774-6.544c.341-2.418.93-2.945 2.418-2.945h7.472c1.428 0 2.264.837 2.264 1.953 0 2.14-1.611 2.326-2.17 2.326h-5.829l-.466 3.286z"
|
||||
/>
|
||||
</Icon>
|
||||
</HTML5BadgeLayout>
|
||||
);
|
||||
} else if (delayedUsesHTML5 === false) {
|
||||
return (
|
||||
<HTML5BadgeLayout
|
||||
backgroundColor={yellowBackground}
|
||||
borderColor={yellowBorderColor}
|
||||
color={yellowTextColor}
|
||||
aria-label="HTML5 not supported"
|
||||
tooltipLabel={
|
||||
tooltipLabel || (
|
||||
<>
|
||||
This item isn't converted to HTML5 yet, so it might not appear in
|
||||
Neopets.com customization yet. Once it's ready, it could look a
|
||||
bit different than our temporary preview here. It might even be
|
||||
animated!
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<WarningTwoIcon fontSize="xs" marginRight="1" />
|
||||
<Icon viewBox="0 0 36 36" fontSize="xl">
|
||||
{/* From Twemoji Keycap 5 */}
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M16.389 14.489c.744-.155 1.551-.31 2.326-.31 3.752 0 6.418 2.977 6.418 6.604 0 5.178-2.851 8.589-8.216 8.589-2.201 0-6.821-1.427-6.821-4.155 0-1.147.961-2.107 2.108-2.107 1.24 0 2.729 1.984 4.806 1.984 2.17 0 3.288-2.109 3.288-4.062 0-1.86-1.055-3.131-2.977-3.131-1.799 0-2.078 1.023-3.659 1.023-1.209 0-1.829-.93-1.829-1.457 0-.403.062-.713.093-1.054l.774-6.544c.341-2.418.93-2.945 2.418-2.945h7.472c1.428 0 2.264.837 2.264 1.953 0 2.14-1.611 2.326-2.17 2.326h-5.829l-.466 3.286z"
|
||||
/>
|
||||
|
||||
{/* From Twemoji Not Allowed */}
|
||||
<path
|
||||
fill="#DD2E44"
|
||||
opacity="0.75"
|
||||
d="M18 0C8.059 0 0 8.059 0 18s8.059 18 18 18 18-8.059 18-18S27.941 0 18 0zm13 18c0 2.565-.753 4.95-2.035 6.965L11.036 7.036C13.05 5.753 15.435 5 18 5c7.18 0 13 5.821 13 13zM5 18c0-2.565.753-4.95 2.036-6.964l17.929 17.929C22.95 30.247 20.565 31 18 31c-7.179 0-13-5.82-13-13z"
|
||||
/>
|
||||
</Icon>
|
||||
</HTML5BadgeLayout>
|
||||
);
|
||||
} else {
|
||||
// If no `usesHTML5` value has been provided yet, we're empty for now!
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function HTML5BadgeLayout({ children, tooltipLabel, ...props }) {
|
||||
const [isHovered, setIsHovered] = React.useState(false);
|
||||
const [isFocused, setIsFocused] = React.useState(false);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
textAlign="center"
|
||||
fontSize="xs"
|
||||
placement="bottom-start"
|
||||
label={tooltipLabel}
|
||||
// HACK: Chakra tooltips seem inconsistent about staying open when focus
|
||||
// comes from touch events. But I really want this one to work on
|
||||
// mobile!
|
||||
isOpen={isHovered || isFocused}
|
||||
>
|
||||
<Flex
|
||||
align="center"
|
||||
border="1px solid"
|
||||
borderRadius="md"
|
||||
boxShadow="md"
|
||||
paddingX="2"
|
||||
paddingY="1"
|
||||
transition="all 0.2s"
|
||||
tabIndex="0"
|
||||
_focus={{ outline: "none", boxShadow: "outline" }}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default HTML5Badge;
|
Loading…
Reference in a new issue