diff --git a/src/app/HomePage.js b/src/app/HomePage.js
index dcfe161..43f2186 100644
--- a/src/app/HomePage.js
+++ b/src/app/HomePage.js
@@ -379,6 +379,10 @@ function NewItemsSectionContent() {
thumbnailUrl
isNc
isPb
+ speciesThatNeedModels {
+ id
+ name
+ }
}
}
`
@@ -402,29 +406,30 @@ function NewItemsSectionContent() {
);
if (loading) {
+ const footer = ;
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
@@ -448,7 +453,26 @@ function NewItemsSectionContent() {
return (
{newestItems.map((item) => (
-
+ 0 ? (
+
+ Need {item.speciesThatNeedModels.length}1 models
+
+ ) : (
+
+ Fully modeled!
+
+ )
+ }
+ />
))}
);
diff --git a/src/app/components/SquareItemCard.js b/src/app/components/SquareItemCard.js
index 5ff1d0b..56aa209 100644
--- a/src/app/components/SquareItemCard.js
+++ b/src/app/components/SquareItemCard.js
@@ -1,5 +1,6 @@
import React from "react";
import {
+ Box,
Skeleton,
useColorModeValue,
useTheme,
@@ -11,7 +12,12 @@ import { Link } from "react-router-dom";
import { safeImageUrl, useCommonStyles } from "../util";
import { CheckIcon, StarIcon } from "@chakra-ui/icons";
-function SquareItemCard({ item, tradeMatchingMode = null, ...props }) {
+function SquareItemCard({
+ item,
+ tradeMatchingMode = null,
+ footer = null,
+ ...props
+}) {
const outlineShadowValue = useToken("shadows", "outline");
const tradeMatchOwnShadowColor = useColorModeValue("green.500", "green.200");
@@ -64,6 +70,7 @@ function SquareItemCard({ item, tradeMatchingMode = null, ...props }) {
/>
}
boxShadow={tradeMatchShadow}
+ footer={footer}
/>
)}
@@ -74,6 +81,7 @@ function SquareItemCard({ item, tradeMatchingMode = null, ...props }) {
function SquareItemCardLayout({
name,
thumbnailImage,
+ footer,
minHeightNumLines = 2,
boxShadow = null,
}) {
@@ -118,6 +126,7 @@ function SquareItemCardLayout({
>
{name}
+ {footer && {footer}}
)}
@@ -350,7 +359,7 @@ function ItemThumbnailKindBadge({ colorScheme, children }) {
);
}
-export function SquareItemCardSkeleton({ minHeightNumLines }) {
+export function SquareItemCardSkeleton({ minHeightNumLines, footer = null }) {
return (
}
minHeightNumLines={minHeightNumLines}
+ footer={footer}
/>
);
}
diff --git a/src/server/loaders.js b/src/server/loaders.js
index 18b59e8..2f9c570 100644
--- a/src/server/loaders.js
+++ b/src/server/loaders.js
@@ -480,7 +480,100 @@ const buildNewestItemsLoader = (db, loaders) =>
return [entities];
});
-const buildItemsThatNeedModelsLoader = (db) =>
+async function runItemModelingQuery(db, filterToItemIds) {
+ let itemIdsCondition;
+ let itemIdsValues;
+ if (filterToItemIds === "all") {
+ // For all items, we use the condition `1`, which matches everything.
+ itemIdsCondition = "1";
+ itemIdsValues = [];
+ } else {
+ // Or, to filter to certain items, we add their IDs to the WHERE clause.
+ const qs = filterToItemIds.map((_) => "?").join(", ");
+ itemIdsCondition = `(item_id IN (${qs}))`;
+ itemIdsValues = filterToItemIds;
+ }
+
+ return await db.execute(
+ `
+ SELECT T_ITEMS.item_id,
+ T_BODIES.color_id,
+ T_ITEMS.supports_vandagyre,
+ COUNT(*) AS modeled_species_count,
+ GROUP_CONCAT(
+ T_BODIES.species_id
+ ORDER BY T_BODIES.species_id
+ ) AS modeled_species_ids,
+ (
+ SELECT GROUP_CONCAT(DISTINCT species_id ORDER BY species_id)
+ FROM pet_types WHERE color_id = T_BODIES.color_id
+ ) AS all_species_ids_for_this_color
+ FROM (
+ -- NOTE: I found that extracting this as a separate query that runs
+ -- first made things WAAAY faster. Less to join/group, I guess?
+ SELECT DISTINCT items.id AS item_id,
+ swf_assets.body_id AS body_id,
+ -- Vandagyre was added on 2014-11-14, so we add some buffer here.
+ -- TODO: Some later Dyeworks items don't support Vandagyre.
+ -- Add a manual db flag?
+ items.created_at >= "2014-12-01" AS supports_vandagyre
+ FROM items
+ INNER JOIN parents_swf_assets psa ON psa.parent_type = "Item"
+ AND psa.parent_id = items.id
+ INNER JOIN swf_assets ON swf_assets.id = psa.swf_asset_id
+ INNER JOIN item_translations it ON it.item_id = items.id AND it.locale = "en"
+ WHERE items.modeling_status_hint IS NULL AND it.name NOT LIKE "%MME%"
+ AND ${itemIdsCondition}
+ ORDER BY item_id
+ ) T_ITEMS
+ INNER JOIN (
+ SELECT DISTINCT body_id, species_id, color_id
+ FROM pet_types
+ WHERE color_id IN (6, 8, 44, 46)
+ ORDER BY body_id, species_id
+ ) T_BODIES ON T_ITEMS.body_id = T_BODIES.body_id
+ GROUP BY T_ITEMS.item_id, T_BODIES.color_id
+ HAVING NOT (
+ -- No species (either an All Bodies item, or a Capsule type thing)
+ modeled_species_count = 0
+ -- Single species (probably just their item)
+ OR modeled_species_count = 1
+ -- All species modeled (that are compatible with this color)
+ OR modeled_species_ids = all_species_ids_for_this_color
+ -- All species modeled except Vandagyre, for items that don't support it
+ OR (NOT T_ITEMS.supports_vandagyre AND modeled_species_count = 54 AND modeled_species_ids = "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54")
+ )
+ ORDER BY T_ITEMS.item_id;
+ `,
+ [...itemIdsValues]
+ );
+}
+
+const buildSpeciesThatNeedModelsForItemLoader = (db) =>
+ new DataLoader(
+ async (colorIdAndItemIdPairs) => {
+ // Get the requested item IDs, ignoring color for now. Remove duplicates.
+ let itemIds = colorIdAndItemIdPairs.map(({ itemId }) => itemId);
+ itemIds = [...new Set(itemIds)];
+
+ // Run the big modeling query, but filtered to specifically these items.
+ // The filter happens very early in the query, so it runs way faster than
+ // the full modeling query.
+ const [rows] = await runItemModelingQuery(db, itemIds);
+
+ const entities = rows.map(normalizeRow);
+
+ // Finally, the query returned a row for each item combined with each
+ // color built into the query (well, no row when no models needed!). So,
+ // find the right row for each color/item pair, or possibly null!
+ return colorIdAndItemIdPairs.map(({ colorId, itemId }) =>
+ entities.find((e) => e.itemId === itemId && e.colorId === colorId)
+ );
+ },
+ { cacheKeyFn: ({ colorId, itemId }) => `${colorId}-${itemId}` }
+ );
+
+const buildItemsThatNeedModelsLoader = (db, loaders) =>
new DataLoader(async (keys) => {
// Essentially, I want to take easy advantage of DataLoader's caching, for
// this query that can only run one way ^_^` There might be a better way to
@@ -489,62 +582,17 @@ const buildItemsThatNeedModelsLoader = (db) =>
throw new Error(`this loader can only be loaded with the key "all"`);
}
- const [rows] = await db.query(
- `
- SELECT T_ITEMS.item_id,
- T_BODIES.color_id,
- T_ITEMS.supports_vandagyre,
- COUNT(*) AS modeled_species_count,
- GROUP_CONCAT(
- T_BODIES.species_id
- ORDER BY T_BODIES.species_id
- ) AS modeled_species_ids,
- (
- SELECT GROUP_CONCAT(DISTINCT species_id ORDER BY species_id)
- FROM pet_types WHERE color_id = T_BODIES.color_id
- ) AS all_species_ids_for_this_color
- FROM (
- -- NOTE: I found that extracting this as a separate query that runs
- -- first made things WAAAY faster. Less to join/group, I guess?
- SELECT DISTINCT items.id AS item_id,
- swf_assets.body_id AS body_id,
- -- Vandagyre was added on 2014-11-14, so we add some buffer here.
- -- TODO: Some later Dyeworks items don't support Vandagyre.
- -- Add a manual db flag?
- items.created_at >= "2014-12-01" AS supports_vandagyre
- FROM items
- INNER JOIN parents_swf_assets psa ON psa.parent_type = "Item"
- AND psa.parent_id = items.id
- INNER JOIN swf_assets ON swf_assets.id = psa.swf_asset_id
- INNER JOIN item_translations it ON it.item_id = items.id AND it.locale = "en"
- WHERE items.modeling_status_hint IS NULL AND it.name NOT LIKE "%MME%"
- ORDER BY item_id
- ) T_ITEMS
- INNER JOIN (
- SELECT DISTINCT body_id, species_id, color_id
- FROM pet_types
- WHERE color_id IN (6, 8, 44, 46)
- ORDER BY body_id, species_id
- ) T_BODIES ON T_ITEMS.body_id = T_BODIES.body_id
- GROUP BY T_ITEMS.item_id, T_BODIES.color_id
- HAVING NOT (
- -- No species (either an All Bodies item, or a Capsule type thing)
- modeled_species_count = 0
- -- Single species (probably just their item)
- OR modeled_species_count = 1
- -- All species modeled (that are compatible with this color)
- OR modeled_species_ids = all_species_ids_for_this_color
- -- All species modeled except Vandagyre, for items that don't support it
- OR (NOT T_ITEMS.supports_vandagyre AND modeled_species_count = 54 AND modeled_species_ids = "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54")
- )
- ORDER BY T_ITEMS.item_id;
- `
- );
+ const [rows] = await runItemModelingQuery(db, "all");
const entities = rows.map(normalizeRow);
const result = new Map();
for (const { colorId, itemId, ...entity } of entities) {
+ loaders.speciesThatNeedModelsForItemLoader.prime(
+ { colorId, itemId },
+ entity
+ );
+
if (!result.has(colorId)) {
result.set(colorId, new Map());
}
@@ -1309,7 +1357,13 @@ function buildLoaders(db) {
);
loaders.itemSearchItemsLoader = buildItemSearchItemsLoader(db, loaders);
loaders.newestItemsLoader = buildNewestItemsLoader(db, loaders);
- loaders.itemsThatNeedModelsLoader = buildItemsThatNeedModelsLoader(db);
+ loaders.speciesThatNeedModelsForItemLoader = buildSpeciesThatNeedModelsForItemLoader(
+ db
+ );
+ loaders.itemsThatNeedModelsLoader = buildItemsThatNeedModelsLoader(
+ db,
+ loaders
+ );
loaders.itemBodiesWithAppearanceDataLoader = buildItemBodiesWithAppearanceDataLoader(
db
);
diff --git a/src/server/types/Item.js b/src/server/types/Item.js
index 042551e..49dbad7 100644
--- a/src/server/types/Item.js
+++ b/src/server/types/Item.js
@@ -457,13 +457,14 @@ const resolvers = {
speciesThatNeedModels: async (
{ id },
{ colorId = "8" }, // Blue
- { itemsThatNeedModelsLoader }
+ { speciesThatNeedModelsForItemLoader }
) => {
- const speciesIdsByColorIdAndItemId = await itemsThatNeedModelsLoader.load(
- "all"
- );
- const speciesIdsByItemId = speciesIdsByColorIdAndItemId.get(colorId);
- const row = speciesIdsByItemId && speciesIdsByItemId.get(id);
+ // NOTE: If we're running this in the context of `itemsThatNeedModels`,
+ // this loader should already be primed, no extra query!
+ const row = await speciesThatNeedModelsForItemLoader.load({
+ itemId: id,
+ colorId,
+ });
if (!row) {
return [];
}