stop using hardcoded items entirely!

This commit is contained in:
Matt Dunn-Rankin 2020-04-24 19:16:24 -07:00
parent abfe854756
commit a9d50bd0c3
5 changed files with 80 additions and 114 deletions

View file

@ -8,10 +8,12 @@ import { Delay } from "./util";
import "./OutfitPreview.css"; import "./OutfitPreview.css";
function OutfitPreview({ itemIds, speciesId, colorId }) { function OutfitPreview({ outfitState }) {
const { wornItemIds, speciesId, colorId } = outfitState;
const { loading, error, data } = useQuery( const { loading, error, data } = useQuery(
gql` gql`
query($itemIds: [ID!]!, $speciesId: ID!, $colorId: ID!) { query($wornItemIds: [ID!]!, $speciesId: ID!, $colorId: ID!) {
petAppearance(speciesId: $speciesId, colorId: $colorId) { petAppearance(speciesId: $speciesId, colorId: $colorId) {
layers { layers {
id id
@ -27,7 +29,7 @@ function OutfitPreview({ itemIds, speciesId, colorId }) {
} }
} }
items(ids: $itemIds) { items(ids: $wornItemIds) {
id id
appearanceOn(speciesId: $speciesId, colorId: $colorId) { appearanceOn(speciesId: $speciesId, colorId: $colorId) {
layers { layers {
@ -47,7 +49,7 @@ function OutfitPreview({ itemIds, speciesId, colorId }) {
} }
`, `,
{ {
variables: { itemIds, speciesId, colorId }, variables: { wornItemIds, speciesId, colorId },
} }
); );

View file

@ -19,7 +19,6 @@ import {
useToast, useToast,
} from "@chakra-ui/core"; } from "@chakra-ui/core";
import { ITEMS } from "./data";
import ItemList, { ItemListSkeleton } from "./ItemList"; import ItemList, { ItemListSkeleton } from "./ItemList";
import useItemData from "./useItemData"; import useItemData from "./useItemData";
import useOutfitState from "./useOutfitState.js"; import useOutfitState from "./useOutfitState.js";
@ -27,7 +26,7 @@ import OutfitPreview from "./OutfitPreview";
import { Delay } from "./util"; import { Delay } from "./util";
function WardrobePage() { function WardrobePage() {
const { loading, error, data, dispatch: dispatchToOutfit } = useOutfitState(); const { loading, error, outfitState, dispatchToOutfit } = useOutfitState();
const [searchQuery, setSearchQuery] = React.useState(""); const [searchQuery, setSearchQuery] = React.useState("");
const toast = useToast(); const toast = useToast();
@ -67,11 +66,7 @@ function WardrobePage() {
width="100%" width="100%"
> >
<Box gridArea="outfit" backgroundColor="gray.900"> <Box gridArea="outfit" backgroundColor="gray.900">
<OutfitPreview <OutfitPreview outfitState={outfitState} />
itemIds={data.wornItemIds}
speciesId={data.speciesId}
colorId={data.colorId}
/>
</Box> </Box>
<Box gridArea="search" boxShadow="sm"> <Box gridArea="search" boxShadow="sm">
<Box px="5" py="3"> <Box px="5" py="3">
@ -83,13 +78,13 @@ function WardrobePage() {
{searchQuery ? ( {searchQuery ? (
<SearchPanel <SearchPanel
query={searchQuery} query={searchQuery}
wornItemIds={data.wornItemIds} outfitState={outfitState}
dispatchToOutfit={dispatchToOutfit} dispatchToOutfit={dispatchToOutfit}
/> />
) : ( ) : (
<ItemsPanel <ItemsPanel
zonesAndItems={data.zonesAndItems}
loading={loading} loading={loading}
outfitState={outfitState}
dispatchToOutfit={dispatchToOutfit} dispatchToOutfit={dispatchToOutfit}
/> />
)} )}
@ -135,8 +130,13 @@ function SearchToolbar({ query, onChange }) {
); );
} }
function SearchPanel({ query, wornItemIds, dispatchToOutfit }) { function SearchPanel({ query, outfitState, dispatchToOutfit }) {
const { loading, error, itemsById } = useItemData(ITEMS.map((i) => i.id)); const { allItemIds, wornItemIds, speciesId, colorId } = outfitState;
const { loading, error, itemsById } = useItemData(
allItemIds,
speciesId,
colorId
);
const normalize = (s) => s.toLowerCase(); const normalize = (s) => s.toLowerCase();
const results = Object.values(itemsById).filter((item) => const results = Object.values(itemsById).filter((item) =>
@ -172,7 +172,7 @@ function SearchResults({
if (error) { if (error) {
return ( return (
<Text color="green.500"> <Text color="green.500">
We hit an error trying to load your search results We hit an error trying to load your search results{" "}
<span role="img" aria-label="(sweat emoji)"> <span role="img" aria-label="(sweat emoji)">
😓 😓
</span>{" "} </span>{" "}
@ -202,7 +202,9 @@ function SearchResults({
); );
} }
function ItemsPanel({ zonesAndItems, loading, dispatchToOutfit }) { function ItemsPanel({ outfitState, loading, dispatchToOutfit }) {
const { zonesAndItems, wornItemIds } = outfitState;
return ( return (
<Box color="green.800"> <Box color="green.800">
<OutfitHeading /> <OutfitHeading />
@ -217,12 +219,14 @@ function ItemsPanel({ zonesAndItems, loading, dispatchToOutfit }) {
</Box> </Box>
))} ))}
{!loading && {!loading &&
zonesAndItems.map(({ zoneName, items, wornItemId }) => ( zonesAndItems.map(({ zone, items }) => (
<Box key={zoneName}> <Box key={zone.id}>
<Heading2 mb="3">{zoneName}</Heading2> <Heading2 mb="3">{zone.label}</Heading2>
<ItemList <ItemList
items={items} items={items}
wornItemIds={[wornItemId]} wornItemIds={items
.map((i) => i.id)
.filter((id) => wornItemIds.includes(id))}
dispatchToOutfit={dispatchToOutfit} dispatchToOutfit={dispatchToOutfit}
/> />
</Box> </Box>

View file

@ -1,56 +0,0 @@
export const ITEMS = [
{
id: "38913",
// name: "Zafara Agent Gloves",
// thumbnailSrc: "http://images.neopets.com/items/clo_zafara_agent_gloves.gif",
zoneName: "Gloves",
},
{
id: "38911",
// name: "Zafara Agent Hood",
// thumbnailSrc: "http://images.neopets.com/items/clo_zafara_agent_hood.gif",
zoneName: "Hat",
},
{
id: "38912",
// name: "Zafara Agent Robe",
// thumbnailSrc: "http://images.neopets.com/items/clo_zafara_agent_robe.gif",
zoneName: "Jacket",
},
{
id: "37375",
// name: "Moon and Stars Background",
// thumbnailSrc: "http://images.neopets.com/items/bg_moonstars.gif",
zoneName: "Background",
},
{
id: "74166",
// name: "Altador Forest Background",
// thumbnailSrc: "http://images.neopets.com/items/bg_ddy18_altadorforest.gif",
zoneName: "Background",
},
{
id: "48313",
// name: "Altador Cup Brooch",
// thumbnailSrc: "http://images.neopets.com/items/clo_altcuplogo_brooch.gif",
zoneName: "Collar",
},
{
id: "37229",
// name: "Magic Ball Table",
// thumbnailSrc: "http://images.neopets.com/items/gif_magicball_table.gif",
zoneName: "Lower Foreground Item",
},
{
id: "43014",
// name: "Green Leaf String Lights",
// thumbnailSrc: "http://images.neopets.com/items/toy_stringlight_illleaf.gif",
zoneName: "Background Item",
},
{
id: "43397",
// name: "Jewelled Staff",
// thumbnailSrc: "http://images.neopets.com/items/mall_staff_jewelled.gif",
zoneName: "Left-hand item",
},
];

View file

@ -1,8 +1,6 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import { useQuery } from "@apollo/react-hooks"; import { useQuery } from "@apollo/react-hooks";
import { ITEMS } from "./data";
function useItemData(itemIds, speciesId, colorId) { function useItemData(itemIds, speciesId, colorId) {
const { loading, error, data } = useQuery( const { loading, error, data } = useQuery(
gql` gql`
@ -12,12 +10,13 @@ function useItemData(itemIds, speciesId, colorId) {
name name
thumbnailUrl thumbnailUrl
# This is used for wearItem actions, to resolve conflicts. We don't # This is used to group items by zone, and to detect conflicts when
# use it directly; we just expect it to be in the cache! # wearing a new item.
appearanceOn(speciesId: $speciesId, colorId: $colorId) { appearanceOn(speciesId: $speciesId, colorId: $colorId) {
layers { layers {
zone { zone {
id id
label
} }
} }
} }
@ -30,11 +29,7 @@ function useItemData(itemIds, speciesId, colorId) {
const items = (data && data.items) || []; const items = (data && data.items) || [];
const itemsById = {}; const itemsById = {};
for (const item of items) { for (const item of items) {
const hardcodedItem = ITEMS.find((i) => i.id === item.id); itemsById[item.id] = item;
itemsById[item.id] = {
...hardcodedItem,
...item,
};
} }
return { loading, error, itemsById }; return { loading, error, itemsById };

View file

@ -9,21 +9,24 @@ enableMapSet();
function useOutfitState() { function useOutfitState() {
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
const [state, dispatch] = React.useReducer(outfitStateReducer(apolloClient), { const [state, dispatchToOutfit] = React.useReducer(
wornItemIds: new Set([ outfitStateReducer(apolloClient),
"38913", {
"38911", wornItemIds: new Set([
"38912", "38913",
"37375", "38911",
"48313", "38912",
"37229", "37375",
"43014", "48313",
"43397", "37229",
]), "43014",
closetedItemIds: new Set(["74166"]), "43397",
speciesId: "54", // Starry ]),
colorId: "75", // Zafara closetedItemIds: new Set(["74166", "68626", "40319"]),
}); speciesId: "54", // Starry
colorId: "75", // Zafara
}
);
const { speciesId, colorId } = state; const { speciesId, colorId } = state;
@ -45,16 +48,22 @@ function useOutfitState() {
closetedItemIds closetedItemIds
); );
const data = { zonesAndItems, wornItemIds, speciesId, colorId }; const outfitState = {
zonesAndItems,
wornItemIds,
allItemIds,
speciesId,
colorId,
};
return { loading, error, data, dispatch }; return { loading, error, outfitState, dispatchToOutfit };
} }
const outfitStateReducer = (apolloClient) => (baseState, action) => { const outfitStateReducer = (apolloClient) => (baseState, action) => {
switch (action.type) { switch (action.type) {
case "wearItem": case "wearItem":
return produce(baseState, (state) => { return produce(baseState, (state) => {
const { wornItemIds, closetedItemIds, speciesId, colorId } = state; const { wornItemIds, closetedItemIds } = state;
const { itemId } = action; const { itemId } = action;
// Move the item out of the closet. // Move the item out of the closet.
@ -131,6 +140,7 @@ function findItemConflicts(itemIdToAdd, state, apolloClient) {
return conflictingIds; return conflictingIds;
} }
// TODO: Get this out of here, tbh...
function getZonesAndItems(itemsById, wornItemIds, closetedItemIds) { function getZonesAndItems(itemsById, wornItemIds, closetedItemIds) {
const wornItems = wornItemIds.map((id) => itemsById[id]).filter((i) => i); const wornItems = wornItemIds.map((id) => itemsById[id]).filter((i) => i);
const closetedItems = closetedItemIds const closetedItems = closetedItemIds
@ -138,17 +148,28 @@ function getZonesAndItems(itemsById, wornItemIds, closetedItemIds) {
.filter((i) => i); .filter((i) => i);
const allItems = [...wornItems, ...closetedItems]; const allItems = [...wornItems, ...closetedItems];
const allZoneNames = [...new Set(allItems.map((item) => item.zoneName))]; const zonesById = new Map();
allZoneNames.sort(); const itemsByZoneId = new Map();
for (const item of allItems) {
for (const layer of item.appearanceOn.layers) {
const zoneId = layer.zone.id;
zonesById.set(zoneId, layer.zone);
const zonesAndItems = allZoneNames.map((zoneName) => { if (!itemsByZoneId.has(zoneId)) {
const items = allItems.filter((item) => item.zoneName === zoneName); itemsByZoneId.set(zoneId, []);
items.sort((a, b) => a.name.localeCompare(b.name)); }
const wornItemId = itemsByZoneId.get(zoneId).push(item);
items.map((item) => item.id).find((id) => wornItemIds.includes(id)) || }
null; }
return { zoneName, items, wornItemId };
}); const zonesAndItems = Array.from(itemsByZoneId.entries()).map(
([zoneId, items]) => ({
zone: zonesById.get(zoneId),
items: [...items].sort((a, b) => a.name.localeCompare(b.name)),
})
);
zonesAndItems.sort((a, b) => a.zone.label.localeCompare(b.zone.label));
return zonesAndItems; return zonesAndItems;
} }