Support alt styles in outfit thumbnails
Oops, before this change, outfits with alt styles would still show the outfit as if no alt style were applied! Now, we have the `Outfit` GraphQL type be internally aware of alt styles, and set its `petAppearance` and `body` and `itemAppearances` fields accordingly. No change was required to the actual `/api/outfitImage` endpoint, once the GraphQL started returning the right thing! …because of that, I'm honestly kinda surprised that there's no obvious issues arising with the Impress 2020 outfit interface itself? But it seems to be correctly just, not showing alt styles at all, in the way I intended because I never added support to it. So, okay, cool!
This commit is contained in:
parent
dc954d7c3c
commit
98eb14853c
3 changed files with 95 additions and 30 deletions
|
@ -1039,6 +1039,29 @@ const buildPetSwfAssetLoader = (db, loaders) =>
|
|||
);
|
||||
});
|
||||
|
||||
const buildAltStyleSwfAssetLoader = (db, loaders) =>
|
||||
new DataLoader(async (altStyleIds) => {
|
||||
const qs = altStyleIds.map((_) => "?").join(",");
|
||||
const [rows] = await db.execute(
|
||||
`SELECT sa.*, rel.parent_id FROM swf_assets sa
|
||||
INNER JOIN parents_swf_assets rel ON
|
||||
rel.parent_type = "AltStyle" AND
|
||||
rel.swf_asset_id = sa.id
|
||||
WHERE rel.parent_id IN (${qs})`,
|
||||
altStyleIds,
|
||||
);
|
||||
|
||||
const entities = rows.map(normalizeRow);
|
||||
|
||||
for (const swfAsset of entities) {
|
||||
loaders.swfAssetLoader.prime(swfAsset.id, swfAsset);
|
||||
}
|
||||
|
||||
return altStyleIds.map((altStyleId) =>
|
||||
entities.filter((e) => e.parentId === altStyleId),
|
||||
);
|
||||
});
|
||||
|
||||
const buildNeopetsConnectionLoader = (db) =>
|
||||
new DataLoader(async (ids) => {
|
||||
const qs = ids.map((_) => "?").join(", ");
|
||||
|
@ -1490,6 +1513,7 @@ function buildLoaders(db) {
|
|||
loaders.swfAssetByRemoteIdLoader = buildSwfAssetByRemoteIdLoader(db);
|
||||
loaders.itemSwfAssetLoader = buildItemSwfAssetLoader(db, loaders);
|
||||
loaders.petSwfAssetLoader = buildPetSwfAssetLoader(db, loaders);
|
||||
loaders.altStyleSwfAssetLoader = buildAltStyleSwfAssetLoader(db, loaders);
|
||||
loaders.neopetsConnectionLoader = buildNeopetsConnectionLoader(db);
|
||||
loaders.outfitLoader = buildOutfitLoader(db);
|
||||
loaders.itemOutfitRelationshipsLoader =
|
||||
|
|
|
@ -62,9 +62,9 @@ const resolvers = {
|
|||
const outfit = await outfitLoader.load(id);
|
||||
return outfit.name;
|
||||
},
|
||||
petAppearance: async ({ id }, _, { outfitLoader }) => {
|
||||
petAppearance: async ({ id }, _, { outfitLoader, altStyleLoader }) => {
|
||||
const outfit = await outfitLoader.load(id);
|
||||
return { id: outfit.petStateId };
|
||||
return { id: outfit.petStateId, altStyleId: outfit.altStyleId };
|
||||
},
|
||||
itemAppearances: async (
|
||||
{ id },
|
||||
|
@ -73,15 +73,26 @@ const resolvers = {
|
|||
outfitLoader,
|
||||
petStateLoader,
|
||||
petTypeLoader,
|
||||
altStyleLoader,
|
||||
itemOutfitRelationshipsLoader,
|
||||
}
|
||||
},
|
||||
) => {
|
||||
const [petType, relationships] = await Promise.all([
|
||||
outfitLoader
|
||||
.load(id)
|
||||
.then((outfit) => petStateLoader.load(outfit.petStateId))
|
||||
.then((petState) => petTypeLoader.load(petState.petTypeId)),
|
||||
itemOutfitRelationshipsLoader.load(id),
|
||||
const relationshipsPromise = itemOutfitRelationshipsLoader.load(id);
|
||||
|
||||
const outfit = await outfitLoader.load(id);
|
||||
const bodyIdPromise =
|
||||
outfit.altStyleId != null
|
||||
? altStyleLoader
|
||||
.load(outfit.altStyleId)
|
||||
.then((altStyle) => altStyle.bodyId)
|
||||
: petStateLoader
|
||||
.load(outfit.petStateId)
|
||||
.then((petState) => petTypeLoader.load(petState.petTypeId))
|
||||
.then((petType) => petType.bodyId);
|
||||
|
||||
const [bodyId, relationships] = await Promise.all([
|
||||
bodyIdPromise,
|
||||
relationshipsPromise,
|
||||
]);
|
||||
|
||||
const wornItemIds = relationships
|
||||
|
@ -90,7 +101,7 @@ const resolvers = {
|
|||
|
||||
return wornItemIds.map((itemId) => ({
|
||||
item: { id: itemId },
|
||||
bodyId: petType.bodyId,
|
||||
bodyId,
|
||||
}));
|
||||
},
|
||||
wornItems: async ({ id }, _, { itemOutfitRelationshipsLoader }) => {
|
||||
|
@ -157,11 +168,11 @@ const resolvers = {
|
|||
outfitLoader,
|
||||
petTypeBySpeciesAndColorLoader,
|
||||
petStatesForPetTypeLoader,
|
||||
}
|
||||
},
|
||||
) => {
|
||||
if (!currentUserId) {
|
||||
throw new Error(
|
||||
"saveOutfit requires login for now. This might change!"
|
||||
"saveOutfit requires login for now. This might change!",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -172,7 +183,7 @@ const resolvers = {
|
|||
}
|
||||
if (outfit.userId !== currentUserId) {
|
||||
throw new Error(
|
||||
`user ${currentUserId} does not own outfit ${outfit.id}`
|
||||
`user ${currentUserId} does not own outfit ${outfit.id}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -181,7 +192,7 @@ const resolvers = {
|
|||
// suffixes.
|
||||
const baseName = (rawName || "Untitled outfit").replace(
|
||||
/\s*\([0-9]+\)\s*$/,
|
||||
""
|
||||
"",
|
||||
);
|
||||
const namePlaceholder = baseName.trim().replace(/_%/g, "\\$0") + "%";
|
||||
|
||||
|
@ -192,10 +203,10 @@ const resolvers = {
|
|||
`
|
||||
SELECT name FROM outfits WHERE user_id = ? AND name LIKE ? AND id != ?;
|
||||
`,
|
||||
[currentUserId, namePlaceholder, id || "<no-ID-new-outfit>"]
|
||||
[currentUserId, namePlaceholder, id || "<no-ID-new-outfit>"],
|
||||
);
|
||||
const existingOutfitNames = new Set(
|
||||
outfitRows.map(({ name }) => name.trim())
|
||||
outfitRows.map(({ name }) => name.trim()),
|
||||
);
|
||||
|
||||
// Then, get the unique name to use for this outfit: try the provided
|
||||
|
@ -213,7 +224,7 @@ const resolvers = {
|
|||
});
|
||||
if (!petType) {
|
||||
throw new Error(
|
||||
`could not find pet type for species=${speciesId}, color=${colorId}`
|
||||
`could not find pet type for species=${speciesId}, color=${colorId}`,
|
||||
);
|
||||
}
|
||||
// TODO: We could query for this more directly, instead of loading all
|
||||
|
@ -222,7 +233,7 @@ const resolvers = {
|
|||
const petState = petStates.find((ps) => getPoseFromPetState(ps) === pose);
|
||||
if (!petState) {
|
||||
throw new Error(
|
||||
`could not find appearance for species=${speciesId}, color=${colorId}, pose=${pose}`
|
||||
`could not find appearance for species=${speciesId}, color=${colorId}, pose=${pose}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -242,7 +253,7 @@ const resolvers = {
|
|||
updated_at = CURRENT_TIMESTAMP()
|
||||
WHERE id = ?;
|
||||
`,
|
||||
[name, petState.id, id]
|
||||
[name, petState.id, id],
|
||||
)
|
||||
: await connection.execute(
|
||||
`
|
||||
|
@ -250,7 +261,7 @@ const resolvers = {
|
|||
(name, pet_state_id, user_id, created_at, updated_at)
|
||||
VALUES (?, ?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP());
|
||||
`,
|
||||
[name, petState.id, currentUserId]
|
||||
[name, petState.id, currentUserId],
|
||||
);
|
||||
newOutfitId = id || String(result.insertId);
|
||||
|
||||
|
@ -264,14 +275,14 @@ const resolvers = {
|
|||
if (id) {
|
||||
await connection.execute(
|
||||
`DELETE FROM item_outfit_relationships WHERE outfit_id = ?;`,
|
||||
[id]
|
||||
[id],
|
||||
);
|
||||
}
|
||||
|
||||
if (wornItemIds.length > 0 || closetedItemIds.length > 0) {
|
||||
const itemRowPlaceholders = [
|
||||
[...wornItemIds, ...closetedItemIds].map(
|
||||
(_) => `(?, ?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP())`
|
||||
(_) => `(?, ?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP())`,
|
||||
),
|
||||
].join(", ");
|
||||
const itemRowValues = [
|
||||
|
@ -286,7 +297,7 @@ const resolvers = {
|
|||
(outfit_id, item_id, is_worn, created_at, updated_at)
|
||||
VALUES ${itemRowPlaceholders};
|
||||
`,
|
||||
itemRowValues
|
||||
itemRowValues,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -314,7 +325,7 @@ const resolvers = {
|
|||
}
|
||||
if (outfit.userId !== currentUserId) {
|
||||
throw new Error(
|
||||
`user ${currentUserId} does not own outfit ${outfit.id}`
|
||||
`user ${currentUserId} does not own outfit ${outfit.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -279,13 +279,29 @@ const resolvers = {
|
|||
const petType = await petTypeLoader.load(petState.petTypeId);
|
||||
return { id: petType.speciesId };
|
||||
},
|
||||
body: async ({ id }, _, { petStateLoader, petTypeLoader }) => {
|
||||
body: async (
|
||||
{ id, altStyleId },
|
||||
_,
|
||||
{ petStateLoader, petTypeLoader, altStyleLoader },
|
||||
) => {
|
||||
const petState = await petStateLoader.load(id);
|
||||
if (altStyleId != null) {
|
||||
const altStyle = await altStyleLoader.load(altStyleId);
|
||||
return { id: altStyle.bodyId };
|
||||
}
|
||||
const petType = await petTypeLoader.load(petState.petTypeId);
|
||||
return { id: petType.bodyId };
|
||||
},
|
||||
bodyId: async ({ id }, _, { petStateLoader, petTypeLoader }) => {
|
||||
bodyId: async (
|
||||
{ id, altStyleId },
|
||||
_,
|
||||
{ petStateLoader, petTypeLoader, altStyleLoader },
|
||||
) => {
|
||||
const petState = await petStateLoader.load(id);
|
||||
if (altStyleId != null) {
|
||||
const altStyle = await altStyleLoader.load(altStyleId);
|
||||
return altStyle.bodyId;
|
||||
}
|
||||
const petType = await petTypeLoader.load(petState.petTypeId);
|
||||
return petType.bodyId;
|
||||
},
|
||||
|
@ -293,14 +309,28 @@ const resolvers = {
|
|||
const petState = await petStateLoader.load(id);
|
||||
return getPoseFromPetState(petState);
|
||||
},
|
||||
layers: async ({ id }, _, { petSwfAssetLoader }) => {
|
||||
const swfAssets = await petSwfAssetLoader.load(id);
|
||||
layers: async (
|
||||
{ id, altStyleId },
|
||||
_,
|
||||
{ petSwfAssetLoader, altStyleSwfAssetLoader },
|
||||
) => {
|
||||
const swfAssets =
|
||||
altStyleId != null
|
||||
? await altStyleSwfAssetLoader.load(altStyleId)
|
||||
: await petSwfAssetLoader.load(id);
|
||||
return swfAssets;
|
||||
},
|
||||
restrictedZones: async ({ id }, _, { petSwfAssetLoader }) => {
|
||||
restrictedZones: async (
|
||||
{ id, altStyleId },
|
||||
_,
|
||||
{ petSwfAssetLoader, altStyleSwfAssetLoader },
|
||||
) => {
|
||||
// The restricted zones are defined on the layers. Load them and aggegate
|
||||
// the zones, then uniquify and sort them for ease of use.
|
||||
const swfAssets = await petSwfAssetLoader.load(id);
|
||||
const swfAssets =
|
||||
altStyleId != null
|
||||
? await altStyleSwfAssetLoader.load(altStyleId)
|
||||
: await petSwfAssetLoader.load(id);
|
||||
let restrictedZoneIds = swfAssets
|
||||
.map((sa) => getRestrictedZoneIds(sa.zonesRestrict))
|
||||
.flat();
|
||||
|
|
Loading…
Reference in a new issue