diff --git a/dev-todos.txt b/dev-todos.txt
index 33f3e64..fac3506 100644
--- a/dev-todos.txt
+++ b/dev-todos.txt
@@ -1,4 +1,5 @@
* Use accessible click targets for item lists! Honestly, can they be checkboxes?
* Pagination for search queries, right now we LIMIT 30
-* Search needs to restrict by fit!
+* Update skeletons for ItemList and ItemsPanel
+* Merge zones with the same name
* Undo the local linking we did for @chakra-ui/core, react, and react-dom on Matchu's machine 😅
diff --git a/src/ItemsPanel.js b/src/ItemsPanel.js
index 6429243..ff8529d 100644
--- a/src/ItemsPanel.js
+++ b/src/ItemsPanel.js
@@ -17,7 +17,7 @@ import ItemList, { ItemListSkeleton } from "./ItemList";
import "./ItemsPanel.css";
function ItemsPanel({ outfitState, loading, dispatchToOutfit }) {
- const { zonesAndItems, wornItemIds } = outfitState;
+ const { zonesAndItems } = outfitState;
return (
diff --git a/src/SearchPanel.js b/src/SearchPanel.js
index 9408a6c..41b89bc 100644
--- a/src/SearchPanel.js
+++ b/src/SearchPanel.js
@@ -10,7 +10,7 @@ import { itemAppearanceFragment } from "./OutfitPreview";
function SearchPanel({ query, outfitState, dispatchToOutfit }) {
return (
- Searching for "{query}"
+ Searching for "{query}"
-
-
- {searchQuery ? (
+
+ {searchQuery ? (
+
+
- ) : (
+
+
+ ) : (
+
+
- )}
+
-
+ )}
);
diff --git a/src/server/index.js b/src/server/index.js
index 9f5b7ed..098ad7f 100644
--- a/src/server/index.js
+++ b/src/server/index.js
@@ -37,6 +37,7 @@ const typeDefs = gql`
type Query {
items(ids: [ID!]!): [Item!]!
itemSearch(query: String!): [Item!]!
+ itemSearchToFit(query: String!, speciesId: ID!, colorId: ID!): [Item!]!
petAppearance(speciesId: ID!, colorId: ID!): Appearance
}
`;
@@ -44,6 +45,9 @@ const typeDefs = gql`
const resolvers = {
Item: {
name: async (item, _, { itemTranslationLoader }) => {
+ // Search queries pre-fill this!
+ if (item.name) return item.name;
+
const translation = await itemTranslationLoader.load(item.id);
return translation.name;
},
@@ -112,6 +116,16 @@ const resolvers = {
const items = await itemSearchLoader.load(query);
return items;
},
+ itemSearchToFit: async (
+ _,
+ { query, speciesId, colorId },
+ { petTypeLoader, itemSearchToFitLoader }
+ ) => {
+ const petType = await petTypeLoader.load({ speciesId, colorId });
+ const { bodyId } = petType;
+ const items = await itemSearchToFitLoader.load({ query, bodyId });
+ return items;
+ },
petAppearance: async (
_,
{ speciesId, colorId },
diff --git a/src/server/index.test.js b/src/server/index.test.js
index 32ad6f4..41da19b 100644
--- a/src/server/index.test.js
+++ b/src/server/index.test.js
@@ -371,11 +371,11 @@ describe("PetAppearance", () => {
});
describe("Search", () => {
- it("loads Zafara Agent items", async () => {
+ it("loads Neopian Times items", async () => {
const res = await query({
query: gql`
query {
- itemSearch(query: "Zafara Agent") {
+ itemSearch(query: "Neopian Times") {
id
name
}
@@ -388,16 +388,52 @@ describe("Search", () => {
Object {
"itemSearch": Array [
Object {
- "id": "38913",
- "name": "Zafara Agent Gloves",
+ "id": "40431",
+ "name": "Neopian Times Background",
},
Object {
- "id": "38911",
- "name": "Zafara Agent Hood",
+ "id": "59391",
+ "name": "Neopian Times Eyrie Hat",
},
Object {
- "id": "38912",
- "name": "Zafara Agent Robe",
+ "id": "59392",
+ "name": "Neopian Times Eyrie Shirt and Vest",
+ },
+ Object {
+ "id": "59394",
+ "name": "Neopian Times Eyrie Shoes",
+ },
+ Object {
+ "id": "59393",
+ "name": "Neopian Times Eyrie Trousers",
+ },
+ Object {
+ "id": "59390",
+ "name": "Neopian Times Eyries Paper",
+ },
+ Object {
+ "id": "51098",
+ "name": "Neopian Times Writing Quill",
+ },
+ Object {
+ "id": "61101",
+ "name": "Neopian Times Zafara Handkerchief",
+ },
+ Object {
+ "id": "61100",
+ "name": "Neopian Times Zafara Hat",
+ },
+ Object {
+ "id": "61102",
+ "name": "Neopian Times Zafara Shirt and Vest",
+ },
+ Object {
+ "id": "61104",
+ "name": "Neopian Times Zafara Shoes",
+ },
+ Object {
+ "id": "61103",
+ "name": "Neopian Times Zafara Trousers",
},
],
}
@@ -405,21 +441,92 @@ describe("Search", () => {
expect(queryFn.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
- "SELECT items.* FROM items
+ "SELECT items.*, t.name FROM items
INNER JOIN item_translations t ON t.item_id = items.id
- WHERE t.name LIKE ? AND locale=\\"en\\"
+ WHERE t.name LIKE ? AND t.locale=\\"en\\"
ORDER BY t.name
LIMIT 30",
Array [
- "%Zafara Agent%",
+ "%Neopian Times%",
+ ],
+ ],
+ ]
+ `);
+ });
+
+ it("loads Neopian Times items that fit the Starry Zafara", async () => {
+ const res = await query({
+ query: gql`
+ query {
+ itemSearchToFit(
+ query: "Neopian Times"
+ speciesId: "54"
+ colorId: "75"
+ ) {
+ id
+ name
+ }
+ }
+ `,
+ });
+
+ expect(res).toHaveNoErrors();
+ expect(res.data).toMatchInlineSnapshot(`
+ Object {
+ "itemSearchToFit": Array [
+ Object {
+ "id": "40431",
+ "name": "Neopian Times Background",
+ },
+ Object {
+ "id": "51098",
+ "name": "Neopian Times Writing Quill",
+ },
+ Object {
+ "id": "61101",
+ "name": "Neopian Times Zafara Handkerchief",
+ },
+ Object {
+ "id": "61100",
+ "name": "Neopian Times Zafara Hat",
+ },
+ Object {
+ "id": "61102",
+ "name": "Neopian Times Zafara Shirt and Vest",
+ },
+ Object {
+ "id": "61104",
+ "name": "Neopian Times Zafara Shoes",
+ },
+ Object {
+ "id": "61103",
+ "name": "Neopian Times Zafara Trousers",
+ },
+ ],
+ }
+ `);
+ expect(queryFn.mock.calls).toMatchInlineSnapshot(`
+ Array [
+ Array [
+ "SELECT * FROM pet_types WHERE (species_id = ? AND color_id = ?)",
+ Array [
+ "54",
+ "75",
],
],
Array [
- "SELECT * FROM item_translations WHERE item_id IN (?,?,?) AND locale = \\"en\\"",
+ "SELECT items.*, t.name FROM items
+ INNER JOIN item_translations t ON t.item_id = items.id
+ INNER JOIN parents_swf_assets rel
+ ON rel.parent_type = \\"Item\\" AND rel.parent_id = items.id
+ INNER JOIN swf_assets ON rel.swf_asset_id = swf_assets.id
+ WHERE t.name LIKE ? AND t.locale=\\"en\\" AND
+ (swf_assets.body_id = ? OR swf_assets.body_id = 0)
+ ORDER BY t.name
+ LIMIT 30",
Array [
- "38913",
- "38911",
- "38912",
+ "%Neopian Times%",
+ "180",
],
],
]
diff --git a/src/server/loaders.js b/src/server/loaders.js
index c3230ec..86ca794 100644
--- a/src/server/loaders.js
+++ b/src/server/loaders.js
@@ -42,9 +42,9 @@ const buildItemSearchLoader = (db) =>
const queryPromises = queries.map(async (query) => {
const queryForMysql = "%" + query.replace(/_%/g, "\\$0") + "%";
const [rows, _] = await db.execute(
- `SELECT items.* FROM items
+ `SELECT items.*, t.name FROM items
INNER JOIN item_translations t ON t.item_id = items.id
- WHERE t.name LIKE ? AND locale="en"
+ WHERE t.name LIKE ? AND t.locale="en"
ORDER BY t.name
LIMIT 30`,
[queryForMysql]
@@ -60,6 +60,35 @@ const buildItemSearchLoader = (db) =>
return responses;
});
+const buildItemSearchToFitLoader = (db) =>
+ new DataLoader(async (queryAndBodyIdPairs) => {
+ // This isn't actually optimized as a batch query, we're just using a
+ // DataLoader API consistency with our other loaders!
+ const queryPromises = queryAndBodyIdPairs.map(async ({ query, bodyId }) => {
+ const queryForMysql = "%" + query.replace(/_%/g, "\\$0") + "%";
+ const [rows, _] = await db.execute(
+ `SELECT items.*, t.name FROM items
+ INNER JOIN item_translations t ON t.item_id = items.id
+ INNER JOIN parents_swf_assets rel
+ ON rel.parent_type = "Item" AND rel.parent_id = items.id
+ INNER JOIN swf_assets ON rel.swf_asset_id = swf_assets.id
+ WHERE t.name LIKE ? AND t.locale="en" AND
+ (swf_assets.body_id = ? OR swf_assets.body_id = 0)
+ ORDER BY t.name
+ LIMIT 30`,
+ [queryForMysql, bodyId]
+ );
+
+ const entities = rows.map(normalizeRow);
+
+ return entities;
+ });
+
+ const responses = await Promise.all(queryPromises);
+
+ return responses;
+ });
+
const buildPetTypeLoader = (db) =>
new DataLoader(async (speciesAndColorPairs) => {
const conditions = [];
@@ -200,6 +229,7 @@ function buildLoaders(db) {
itemLoader: buildItemsLoader(db),
itemTranslationLoader: buildItemTranslationLoader(db),
itemSearchLoader: buildItemSearchLoader(db),
+ itemSearchToFitLoader: buildItemSearchToFitLoader(db),
petTypeLoader: buildPetTypeLoader(db),
itemSwfAssetLoader: buildItemSwfAssetLoader(db),
petSwfAssetLoader: buildPetSwfAssetLoader(db),