diff --git a/src/app/OutfitPreview.js b/src/app/OutfitPreview.js index 588d353..e908a55 100644 --- a/src/app/OutfitPreview.js +++ b/src/app/OutfitPreview.js @@ -69,7 +69,7 @@ function OutfitPreview({ outfitState, dispatchToOutfit }) { visibleLayers ); - const { onCopy, hasCopied } = useClipboard(getShareUrl(outfitState)); + const { onCopy, hasCopied } = useClipboard(outfitState.url); if (error) { return ( @@ -378,29 +378,4 @@ 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; diff --git a/src/app/useOutfitState.js b/src/app/useOutfitState.js index 63b799b..42170d6 100644 --- a/src/app/useOutfitState.js +++ b/src/app/useOutfitState.js @@ -35,21 +35,6 @@ 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 @@ -99,6 +84,8 @@ function useOutfitState() { closetedItemIds ); + const url = buildOutfitUrl(state); + const outfitState = { zonesAndItems, name, @@ -107,8 +94,29 @@ function useOutfitState() { allItemIds, speciesId, colorId, + url, }; + // Get the state from the URL the first time we load. + 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[]"), + }); + } + }, []); + + // Afterwards, keep the URL up-to-date, but don't listen to it anymore. + React.useEffect(() => { + window.history.replaceState(null, "", url); + }, [url]); + return { loading, error, outfitState, dispatchToOutfit }; } @@ -306,4 +314,23 @@ function getZonesAndItems(itemsById, wornItemIds, closetedItemIds) { return zonesAndItems; } +function buildOutfitUrl(state) { + const { name, speciesId, colorId, wornItemIds, closetedItemIds } = state; + + 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 useOutfitState;