diff --git a/app/javascript/wardrobe-2020/WardrobePage/OutfitControls.js b/app/javascript/wardrobe-2020/WardrobePage/OutfitControls.js index b931c400..836b20c0 100644 --- a/app/javascript/wardrobe-2020/WardrobePage/OutfitControls.js +++ b/app/javascript/wardrobe-2020/WardrobePage/OutfitControls.js @@ -11,6 +11,9 @@ import { HStack, IconButton, ListItem, + Menu, + MenuItem, + MenuList, Popover, PopoverArrow, PopoverBody, @@ -106,156 +109,197 @@ function OutfitControls({ return ( {({ css, cx }) => ( - + { + const opacity = parseFloat( + getComputedStyle(e.currentTarget).opacity + ); + if (opacity < 0.5) { + // If the controls aren't visible right now, then clicks on them are + // probably accidental. Ignore them! (We prevent default to block + // built-in behaviors like link nav, and we stop propagation to block + // our own custom click handlers. I don't know if I can prevent the + // select clicks though?) + e.preventDefault(); + e.stopPropagation(); + + // We also show the controls, by locking focus. We'll undo this when + // the user taps elsewhere (because it will trigger a blur event from + // our child components), in `maybeUnlockFocus`. + setFocusIsLocked(true); } - `, - focusIsLocked && "focus-is-locked" - )} - onClickCapture={(e) => { - const opacity = parseFloat( - getComputedStyle(e.currentTarget).opacity - ); - if (opacity < 0.5) { - // If the controls aren't visible right now, then clicks on them are - // probably accidental. Ignore them! (We prevent default to block - // built-in behaviors like link nav, and we stop propagation to block - // our own custom click handlers. I don't know if I can prevent the - // select clicks though?) - e.preventDefault(); - e.stopPropagation(); - - // We also show the controls, by locking focus. We'll undo this when - // the user taps elsewhere (because it will trigger a blur event from - // our child components), in `maybeUnlockFocus`. - setFocusIsLocked(true); - } - }} - onContextMenuCapture={() => { - if (!toast.isActive("outfit-controls-context-menu-hint")) { - toast({ - id: "outfit-controls-context-menu-hint", - title: - "By the way, to save this image, use the Download button!", - description: "It's in the top right of the preview area.", - duration: 10000, - isClosable: true, - }); - } - }} - data-test-id="wardrobe-outfit-controls" - > - - - - - - {showAnimationControls && } - - - - - - - - - - + + - - - - - - {outfitState.speciesId && outfitState.colorId && ( - - {/** - * 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! - */} - - - - - - - - + {showAnimationControls && } + + + + + - + - )} - + + + + + + + + + + {outfitState.speciesId && outfitState.colorId && ( + + {/** + * 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! + */} + + + + + + + + + + + )} + + )} ); } +function OutfitControlsContextMenu({ outfitState, children }) { + // NOTE: We track these separately, rather than in one atomic state object, + // because I want to still keep the menu in the right position when it's + // animating itself closed! + const [isOpen, setIsOpen] = React.useState(false); + const [position, setPosition] = React.useState({ x: 0, y: 0 }); + + const { visibleLayers } = useOutfitAppearance(outfitState); + const [downloadImageUrl, prepareDownload] = + useDownloadableImage(visibleLayers); + + return ( + { + setIsOpen(true); + setPosition({ x: e.pageX, y: e.pageY }); + e.preventDefault(); + }} + > + {children} + setIsOpen(false)}> + + + } + as="a" + // eslint-disable-next-line no-script-url + href={downloadImageUrl || "#"} + onClick={(e) => { + if (!downloadImageUrl) { + e.preventDefault(); + } + }} + download={(outfitState.name || "Outfit") + ".png"} + onMouseEnter={prepareDownload} + onFocus={prepareDownload} + cursor={!downloadImageUrl && "wait"} + > + Download + + + + + + ); +} + function OutfitHTML5Badge({ appearance }) { const petIsUsingHTML5 = appearance.petAppearance?.layers.every(layerUsesHTML5);