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) =>
|
const buildNeopetsConnectionLoader = (db) =>
|
||||||
new DataLoader(async (ids) => {
|
new DataLoader(async (ids) => {
|
||||||
const qs = ids.map((_) => "?").join(", ");
|
const qs = ids.map((_) => "?").join(", ");
|
||||||
|
@ -1490,6 +1513,7 @@ function buildLoaders(db) {
|
||||||
loaders.swfAssetByRemoteIdLoader = buildSwfAssetByRemoteIdLoader(db);
|
loaders.swfAssetByRemoteIdLoader = buildSwfAssetByRemoteIdLoader(db);
|
||||||
loaders.itemSwfAssetLoader = buildItemSwfAssetLoader(db, loaders);
|
loaders.itemSwfAssetLoader = buildItemSwfAssetLoader(db, loaders);
|
||||||
loaders.petSwfAssetLoader = buildPetSwfAssetLoader(db, loaders);
|
loaders.petSwfAssetLoader = buildPetSwfAssetLoader(db, loaders);
|
||||||
|
loaders.altStyleSwfAssetLoader = buildAltStyleSwfAssetLoader(db, loaders);
|
||||||
loaders.neopetsConnectionLoader = buildNeopetsConnectionLoader(db);
|
loaders.neopetsConnectionLoader = buildNeopetsConnectionLoader(db);
|
||||||
loaders.outfitLoader = buildOutfitLoader(db);
|
loaders.outfitLoader = buildOutfitLoader(db);
|
||||||
loaders.itemOutfitRelationshipsLoader =
|
loaders.itemOutfitRelationshipsLoader =
|
||||||
|
|
|
@ -62,9 +62,9 @@ const resolvers = {
|
||||||
const outfit = await outfitLoader.load(id);
|
const outfit = await outfitLoader.load(id);
|
||||||
return outfit.name;
|
return outfit.name;
|
||||||
},
|
},
|
||||||
petAppearance: async ({ id }, _, { outfitLoader }) => {
|
petAppearance: async ({ id }, _, { outfitLoader, altStyleLoader }) => {
|
||||||
const outfit = await outfitLoader.load(id);
|
const outfit = await outfitLoader.load(id);
|
||||||
return { id: outfit.petStateId };
|
return { id: outfit.petStateId, altStyleId: outfit.altStyleId };
|
||||||
},
|
},
|
||||||
itemAppearances: async (
|
itemAppearances: async (
|
||||||
{ id },
|
{ id },
|
||||||
|
@ -73,15 +73,26 @@ const resolvers = {
|
||||||
outfitLoader,
|
outfitLoader,
|
||||||
petStateLoader,
|
petStateLoader,
|
||||||
petTypeLoader,
|
petTypeLoader,
|
||||||
|
altStyleLoader,
|
||||||
itemOutfitRelationshipsLoader,
|
itemOutfitRelationshipsLoader,
|
||||||
}
|
},
|
||||||
) => {
|
) => {
|
||||||
const [petType, relationships] = await Promise.all([
|
const relationshipsPromise = itemOutfitRelationshipsLoader.load(id);
|
||||||
outfitLoader
|
|
||||||
.load(id)
|
const outfit = await outfitLoader.load(id);
|
||||||
.then((outfit) => petStateLoader.load(outfit.petStateId))
|
const bodyIdPromise =
|
||||||
.then((petState) => petTypeLoader.load(petState.petTypeId)),
|
outfit.altStyleId != null
|
||||||
itemOutfitRelationshipsLoader.load(id),
|
? 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
|
const wornItemIds = relationships
|
||||||
|
@ -90,7 +101,7 @@ const resolvers = {
|
||||||
|
|
||||||
return wornItemIds.map((itemId) => ({
|
return wornItemIds.map((itemId) => ({
|
||||||
item: { id: itemId },
|
item: { id: itemId },
|
||||||
bodyId: petType.bodyId,
|
bodyId,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
wornItems: async ({ id }, _, { itemOutfitRelationshipsLoader }) => {
|
wornItems: async ({ id }, _, { itemOutfitRelationshipsLoader }) => {
|
||||||
|
@ -157,11 +168,11 @@ const resolvers = {
|
||||||
outfitLoader,
|
outfitLoader,
|
||||||
petTypeBySpeciesAndColorLoader,
|
petTypeBySpeciesAndColorLoader,
|
||||||
petStatesForPetTypeLoader,
|
petStatesForPetTypeLoader,
|
||||||
}
|
},
|
||||||
) => {
|
) => {
|
||||||
if (!currentUserId) {
|
if (!currentUserId) {
|
||||||
throw new Error(
|
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) {
|
if (outfit.userId !== currentUserId) {
|
||||||
throw new Error(
|
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.
|
// suffixes.
|
||||||
const baseName = (rawName || "Untitled outfit").replace(
|
const baseName = (rawName || "Untitled outfit").replace(
|
||||||
/\s*\([0-9]+\)\s*$/,
|
/\s*\([0-9]+\)\s*$/,
|
||||||
""
|
"",
|
||||||
);
|
);
|
||||||
const namePlaceholder = baseName.trim().replace(/_%/g, "\\$0") + "%";
|
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 != ?;
|
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(
|
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
|
// Then, get the unique name to use for this outfit: try the provided
|
||||||
|
@ -213,7 +224,7 @@ const resolvers = {
|
||||||
});
|
});
|
||||||
if (!petType) {
|
if (!petType) {
|
||||||
throw new Error(
|
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
|
// 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);
|
const petState = petStates.find((ps) => getPoseFromPetState(ps) === pose);
|
||||||
if (!petState) {
|
if (!petState) {
|
||||||
throw new Error(
|
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()
|
updated_at = CURRENT_TIMESTAMP()
|
||||||
WHERE id = ?;
|
WHERE id = ?;
|
||||||
`,
|
`,
|
||||||
[name, petState.id, id]
|
[name, petState.id, id],
|
||||||
)
|
)
|
||||||
: await connection.execute(
|
: await connection.execute(
|
||||||
`
|
`
|
||||||
|
@ -250,7 +261,7 @@ const resolvers = {
|
||||||
(name, pet_state_id, user_id, created_at, updated_at)
|
(name, pet_state_id, user_id, created_at, updated_at)
|
||||||
VALUES (?, ?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP());
|
VALUES (?, ?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP());
|
||||||
`,
|
`,
|
||||||
[name, petState.id, currentUserId]
|
[name, petState.id, currentUserId],
|
||||||
);
|
);
|
||||||
newOutfitId = id || String(result.insertId);
|
newOutfitId = id || String(result.insertId);
|
||||||
|
|
||||||
|
@ -264,14 +275,14 @@ const resolvers = {
|
||||||
if (id) {
|
if (id) {
|
||||||
await connection.execute(
|
await connection.execute(
|
||||||
`DELETE FROM item_outfit_relationships WHERE outfit_id = ?;`,
|
`DELETE FROM item_outfit_relationships WHERE outfit_id = ?;`,
|
||||||
[id]
|
[id],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wornItemIds.length > 0 || closetedItemIds.length > 0) {
|
if (wornItemIds.length > 0 || closetedItemIds.length > 0) {
|
||||||
const itemRowPlaceholders = [
|
const itemRowPlaceholders = [
|
||||||
[...wornItemIds, ...closetedItemIds].map(
|
[...wornItemIds, ...closetedItemIds].map(
|
||||||
(_) => `(?, ?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP())`
|
(_) => `(?, ?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP())`,
|
||||||
),
|
),
|
||||||
].join(", ");
|
].join(", ");
|
||||||
const itemRowValues = [
|
const itemRowValues = [
|
||||||
|
@ -286,7 +297,7 @@ const resolvers = {
|
||||||
(outfit_id, item_id, is_worn, created_at, updated_at)
|
(outfit_id, item_id, is_worn, created_at, updated_at)
|
||||||
VALUES ${itemRowPlaceholders};
|
VALUES ${itemRowPlaceholders};
|
||||||
`,
|
`,
|
||||||
itemRowValues
|
itemRowValues,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,7 +325,7 @@ const resolvers = {
|
||||||
}
|
}
|
||||||
if (outfit.userId !== currentUserId) {
|
if (outfit.userId !== currentUserId) {
|
||||||
throw new Error(
|
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);
|
const petType = await petTypeLoader.load(petState.petTypeId);
|
||||||
return { id: petType.speciesId };
|
return { id: petType.speciesId };
|
||||||
},
|
},
|
||||||
body: async ({ id }, _, { petStateLoader, petTypeLoader }) => {
|
body: async (
|
||||||
|
{ id, altStyleId },
|
||||||
|
_,
|
||||||
|
{ petStateLoader, petTypeLoader, altStyleLoader },
|
||||||
|
) => {
|
||||||
const petState = await petStateLoader.load(id);
|
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);
|
const petType = await petTypeLoader.load(petState.petTypeId);
|
||||||
return { id: petType.bodyId };
|
return { id: petType.bodyId };
|
||||||
},
|
},
|
||||||
bodyId: async ({ id }, _, { petStateLoader, petTypeLoader }) => {
|
bodyId: async (
|
||||||
|
{ id, altStyleId },
|
||||||
|
_,
|
||||||
|
{ petStateLoader, petTypeLoader, altStyleLoader },
|
||||||
|
) => {
|
||||||
const petState = await petStateLoader.load(id);
|
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);
|
const petType = await petTypeLoader.load(petState.petTypeId);
|
||||||
return petType.bodyId;
|
return petType.bodyId;
|
||||||
},
|
},
|
||||||
|
@ -293,14 +309,28 @@ const resolvers = {
|
||||||
const petState = await petStateLoader.load(id);
|
const petState = await petStateLoader.load(id);
|
||||||
return getPoseFromPetState(petState);
|
return getPoseFromPetState(petState);
|
||||||
},
|
},
|
||||||
layers: async ({ id }, _, { petSwfAssetLoader }) => {
|
layers: async (
|
||||||
const swfAssets = await petSwfAssetLoader.load(id);
|
{ id, altStyleId },
|
||||||
|
_,
|
||||||
|
{ petSwfAssetLoader, altStyleSwfAssetLoader },
|
||||||
|
) => {
|
||||||
|
const swfAssets =
|
||||||
|
altStyleId != null
|
||||||
|
? await altStyleSwfAssetLoader.load(altStyleId)
|
||||||
|
: await petSwfAssetLoader.load(id);
|
||||||
return swfAssets;
|
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 restricted zones are defined on the layers. Load them and aggegate
|
||||||
// the zones, then uniquify and sort them for ease of use.
|
// 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
|
let restrictedZoneIds = swfAssets
|
||||||
.map((sa) => getRestrictedZoneIds(sa.zonesRestrict))
|
.map((sa) => getRestrictedZoneIds(sa.zonesRestrict))
|
||||||
.flat();
|
.flat();
|
||||||
|
|
Loading…
Reference in a new issue