link sharing ^^

This commit is contained in:
Matt Dunn-Rankin 2020-04-25 07:22:03 -07:00
parent 90ad9feb0c
commit 26244ea776
3 changed files with 135 additions and 47 deletions

View file

@ -10,8 +10,10 @@ import {
Image,
PseudoBox,
Spinner,
Stack,
Text,
Tooltip,
useClipboard,
} from "@chakra-ui/core";
import { Delay } from "./util";
@ -68,6 +70,8 @@ function OutfitPreview({ outfitState, dispatchToOutfit }) {
visibleLayers
);
const { onCopy, hasCopied } = useClipboard(getShareUrl(outfitState));
if (error) {
return (
<FullScreenCenter>
@ -133,9 +137,9 @@ function OutfitPreview({ outfitState, dispatchToOutfit }) {
bottom={{ base: 2, lg: 6 }}
// Grid layout for the content!
display="grid"
gridTemplateAreas={`"space picker download"`}
gridTemplateAreas={`"space picker buttons"`}
gridTemplateColumns="minmax(0, 1fr) auto 1fr"
alignItems="center"
alignItems="flex-end"
>
<Box gridArea="space"></Box>
<PseudoBox
@ -151,46 +155,83 @@ function OutfitPreview({ outfitState, dispatchToOutfit }) {
onBlur={() => setHasFocus(false)}
/>
</PseudoBox>
<Flex gridArea="download" justify="flex-end">
<Tooltip label="Download" placement="left">
<IconButton
icon="download"
aria-label="Download"
isRound
as="a"
// eslint-disable-next-line no-script-url
href={downloadImageUrl || "javascript:void 0"}
download={(outfitState.name || "Outfit") + ".png"}
onMouseEnter={prepareDownload}
onFocus={() => {
prepareDownload();
setHasFocus(true);
}}
onBlur={() => setHasFocus(false)}
cursor={!downloadImageUrl && "wait"}
variant="unstyled"
backgroundColor="gray.600"
color="gray.50"
boxShadow="md"
d="flex"
alignItems="center"
justifyContent="center"
opacity={hasFocus ? 1 : 0}
transition="all 0.2s"
_groupHover={{
opacity: 1,
}}
_focus={{
opacity: 1,
backgroundColor: "gray.500",
}}
_hover={{
backgroundColor: "gray.500",
}}
outline="initial"
/>
</Tooltip>
</Flex>
<Stack gridArea="buttons" spacing="2" align="flex-end">
<Box>
<Tooltip
label={hasCopied ? "Copied!" : "Copy link"}
placement="left"
>
<IconButton
icon={hasCopied ? "check" : "link"}
aria-label="Copy link"
isRound
onClick={onCopy}
onFocus={() => setHasFocus(true)}
onBlur={() => setHasFocus(false)}
variant="unstyled"
backgroundColor="gray.600"
color="gray.50"
boxShadow="md"
d="flex"
alignItems="center"
justifyContent="center"
opacity={hasFocus ? 1 : 0}
transition="all 0.2s"
_groupHover={{
opacity: 1,
}}
_focus={{
opacity: 1,
backgroundColor: "gray.500",
}}
_hover={{
backgroundColor: "gray.500",
}}
outline="initial"
/>
</Tooltip>
</Box>
<Box>
<Tooltip label="Download" placement="left">
<IconButton
icon="download"
aria-label="Download"
isRound
as="a"
// eslint-disable-next-line no-script-url
href={downloadImageUrl || "javascript:void 0"}
download={(outfitState.name || "Outfit") + ".png"}
onMouseEnter={prepareDownload}
onFocus={() => {
prepareDownload();
setHasFocus(true);
}}
onBlur={() => setHasFocus(false)}
cursor={!downloadImageUrl && "wait"}
variant="unstyled"
backgroundColor="gray.600"
color="gray.50"
boxShadow="md"
d="flex"
alignItems="center"
justifyContent="center"
opacity={hasFocus ? 1 : 0}
transition="all 0.2s"
_groupHover={{
opacity: 1,
}}
_focus={{
opacity: 1,
backgroundColor: "gray.500",
}}
_hover={{
backgroundColor: "gray.500",
}}
outline="initial"
/>
</Tooltip>
</Box>
</Stack>
</Box>
<Box pos="absolute" left="3" top="3">
<IconButton
@ -315,4 +356,29 @@ function useDownloadableImage(visibleLayers) {
return [downloadImageUrl, prepareDownload];
}
function getShareUrl(outfitState) {
const {
name,
speciesId,
colorId,
wornItemIds,
closetedItemIds,
} = outfitState;
const params = new URLSearchParams();
params.append("name", name);
params.append("species", speciesId);
params.append("color", colorId);
for (const itemId of wornItemIds) {
params.append("objects[]", itemId);
}
for (const itemId of closetedItemIds) {
params.append("closet[]", itemId);
}
const { origin, pathname } = window.location;
const url = origin + pathname + "?" + params.toString();
return url;
}
export default OutfitPreview;

View file

@ -95,7 +95,9 @@ function SearchResults({ query, outfitState, dispatchToOutfit }) {
offset: items.length,
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
if (!fetchMoreResult || fetchMoreResult.query !== prev.query) {
return prev;
}
// Note: This is a bit awkward because, if the results count ends on
// a multiple of 30, the user will see a flash of loading before

View file

@ -29,6 +29,21 @@ function useOutfitState() {
}
);
React.useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has("species")) {
dispatchToOutfit({
type: "reset",
name: urlParams.get("name"),
speciesId: urlParams.get("species"),
colorId: urlParams.get("color"),
wornItemIds: urlParams.getAll("objects[]"),
closetedItemIds: urlParams.getAll("closet[]"),
});
}
window.history.replaceState(null, "", window.location.href.split("?")[0]);
});
const { name, speciesId, colorId } = state;
// It's more convenient to manage these as a Set in state, but most callers
@ -82,6 +97,7 @@ function useOutfitState() {
zonesAndItems,
name,
wornItemIds,
closetedItemIds,
allItemIds,
speciesId,
colorId,
@ -150,10 +166,14 @@ const outfitStateReducer = (apolloClient) => (baseState, action) => {
const { name, speciesId, colorId, wornItemIds, closetedItemIds } = action;
return {
name,
speciesId: String(speciesId || baseState.speciesId),
colorId: String(colorId || baseState.colorId),
wornItemIds: new Set(wornItemIds.map(String)),
closetedItemIds: new Set(closetedItemIds.map(String)),
speciesId: speciesId ? String(speciesId) : baseState.speciesId,
colorId: colorId ? String(colorId) : baseState.colorId,
wornItemIds: wornItemIds
? new Set(wornItemIds.map(String))
: baseState.wornItemIds,
closetedItemIds: closetedItemIds
? new Set(closetedItemIds.map(String))
: baseState.closetedItemIds,
};
default:
throw new Error(`unexpected action ${JSON.stringify(action)}`);