can submit the actual body ID Support mutation!

it seems to actually be changing the things correctly aaaa
This commit is contained in:
Emi Matchu 2020-08-01 15:30:26 -07:00
parent e8917936d6
commit 8fdc986ee9
4 changed files with 211 additions and 28 deletions

View file

@ -1,4 +1,6 @@
import * as React from "react";
import gql from "graphql-tag";
import { useMutation } from "@apollo/client";
import {
Button,
Box,
@ -17,12 +19,16 @@ import {
Radio,
RadioGroup,
Spinner,
useToast,
} from "@chakra-ui/core";
import { ExternalLinkIcon } from "@chakra-ui/icons";
import { OutfitLayers } from "../../components/OutfitPreview";
import SpeciesColorPicker from "../../components/SpeciesColorPicker";
import useOutfitAppearance from "../../components/useOutfitAppearance";
import useOutfitAppearance, {
itemAppearanceFragment,
} from "../../components/useOutfitAppearance";
import useSupportSecret from "./useSupportSecret";
function ItemSupportAppearanceLayerModal({
item,
@ -31,6 +37,82 @@ function ItemSupportAppearanceLayerModal({
isOpen,
onClose,
}) {
const [selectedBodyId, setSelectedBodyId] = React.useState(itemLayer.bodyId);
const [previewBiology, setPreviewBiology] = React.useState({
speciesId: outfitState.speciesId,
colorId: outfitState.colorId,
pose: outfitState.pose,
isValid: true,
});
const supportSecret = useSupportSecret();
const toast = useToast();
const [
mutate,
{ loading: mutationLoading, error: mutationError },
] = useMutation(
gql`
mutation ItemSupportSetLayerBodyId(
$layerId: ID!
$bodyId: ID!
$supportSecret: String!
$outfitSpeciesId: ID!
$outfitColorId: ID!
$formPreviewSpeciesId: ID!
$formPreviewColorId: ID!
) {
setLayerBodyId(
layerId: $layerId
bodyId: $bodyId
supportSecret: $supportSecret
) {
# This mutation returns the affected AppearanceLayer. Fetch the
# updated fields, including the appearance on the outfit pet and the
# form preview pet, to automatically update our cached appearance in
# the rest of the app. That means you should be able to see your
# changes immediately!
id
bodyId
item {
id
appearanceOnOutfit: appearanceOn(
speciesId: $outfitSpeciesId
colorId: $outfitColorId
) {
...ItemAppearanceForOutfitPreview
}
appearanceOnFormPreviewPet: appearanceOn(
speciesId: $formPreviewSpeciesId
colorId: $formPreviewColorId
) {
...ItemAppearanceForOutfitPreview
}
}
}
}
${itemAppearanceFragment}
`,
{
variables: {
layerId: itemLayer.id,
bodyId: selectedBodyId,
supportSecret,
outfitSpeciesId: outfitState.speciesId,
outfitColorId: outfitState.colorId,
formPreviewSpeciesId: previewBiology.speciesId,
formPreviewColorId: previewBiology.colorId,
},
onCompleted: () => {
onClose();
toast({
status: "success",
title: `Saved layer ${itemLayer.id}: ${item.name}`,
});
},
}
);
return (
<Modal size="xl" isOpen={isOpen} onClose={onClose}>
<ModalOverlay>
@ -41,7 +123,7 @@ function ItemSupportAppearanceLayerModal({
<ModalCloseButton />
<ModalBody>
<Metadata>
<MetadataLabel>ID:</MetadataLabel>
<MetadataLabel>DTI ID:</MetadataLabel>
<MetadataValue>{itemLayer.id}</MetadataValue>
<MetadataLabel>Zone:</MetadataLabel>
<MetadataValue>
@ -88,10 +170,30 @@ function ItemSupportAppearanceLayerModal({
item={item}
itemLayer={itemLayer}
outfitState={outfitState}
selectedBodyId={selectedBodyId}
previewBiology={previewBiology}
onChangeBodyId={setSelectedBodyId}
onChangePreviewBiology={setPreviewBiology}
/>
</ModalBody>
<ModalFooter>
<Button colorScheme="green">Save changes</Button>
{mutationError && (
<Box
color="red.400"
fontSize="sm"
marginRight="2"
textAlign="right"
>
{mutationError.message}
</Box>
)}
<Button
isLoading={mutationLoading}
colorScheme="green"
onClick={mutate}
>
Save changes
</Button>
</ModalFooter>
</ModalContent>
</ModalOverlay>
@ -103,15 +205,12 @@ function ItemSupportAppearanceLayerPetCompatibility({
item,
itemLayer,
outfitState,
selectedBodyId,
previewBiology,
onChangeBodyId,
onChangePreviewBiology,
}) {
const [bodyId, setBodyId] = React.useState(itemLayer.bodyId);
const [selectedBiology, setSelectedBiology] = React.useState({
speciesId: outfitState.speciesId,
colorId: outfitState.colorId,
pose: outfitState.pose,
isValid: true,
});
const [visibleBiology, setVisibleBiology] = React.useState(selectedBiology);
const [selectedBiology, setSelectedBiology] = React.useState(previewBiology);
const {
loading,
@ -119,9 +218,9 @@ function ItemSupportAppearanceLayerPetCompatibility({
visibleLayers,
bodyId: appearanceBodyId,
} = useOutfitAppearance({
speciesId: visibleBiology.speciesId,
colorId: visibleBiology.colorId,
pose: visibleBiology.pose,
speciesId: previewBiology.speciesId,
colorId: previewBiology.colorId,
pose: previewBiology.pose,
wornItemIds: [item.id],
});
@ -130,18 +229,18 @@ function ItemSupportAppearanceLayerPetCompatibility({
// When the appearance body ID changes, select it as the new body ID. (This
// is an effect because it happens after the appearance finishes loading!)
React.useEffect(() => {
if (bodyId !== "0") {
setBodyId(appearanceBodyId);
if (selectedBodyId !== "0") {
onChangeBodyId(appearanceBodyId);
}
}, [bodyId, appearanceBodyId]);
}, [selectedBodyId, appearanceBodyId, onChangeBodyId]);
return (
<FormControl isInvalid={error || !selectedBiology.isValid ? true : false}>
<FormLabel>Pet compatibility</FormLabel>
<RadioGroup
colorScheme="green"
value={bodyId}
onChange={(newBodyId) => setBodyId(newBodyId)}
value={selectedBodyId}
onChange={(newBodyId) => onChangeBodyId(newBodyId)}
marginBottom="4"
>
<Radio value="0">
@ -189,7 +288,7 @@ function ItemSupportAppearanceLayerPetCompatibility({
setSelectedBiology({ speciesId, colorId, isValid, pose });
if (isValid) {
setVisibleBiology({ speciesId, colorId, isValid, pose });
onChangePreviewBiology({ speciesId, colorId, isValid, pose });
}
}}
/>

View file

@ -250,6 +250,7 @@ function ItemSupportAppearanceFields({ item, outfitState }) {
<HStack spacing="4" overflow="auto" paddingX="1">
{itemLayers.map((itemLayer) => (
<ItemSupportAppearanceLayer
key={itemLayer.id}
item={item}
itemLayer={itemLayer}
biologyLayers={biologyLayers}
@ -290,7 +291,7 @@ function ItemSupportAppearanceLayer({
</Box>
<Box fontWeight="bold">{itemLayer.zone.label}</Box>
<Box>Zone ID: {itemLayer.zone.id}</Box>
<Box>Layer ID: {itemLayer.id}</Box>
<Box>DTI ID: {itemLayer.id}</Box>
<Box
className={css`
opacity: 0;

View file

@ -110,6 +110,11 @@ const typeDefs = gql`
special body ID that indicates it fits all PetAppearances.
"""
bodyId: ID!
"""
The item this layer is for, if any. (For pet layers, this is null.)
"""
item: Item
}
# Cache for 1 week (unlikely to change)
@ -184,6 +189,12 @@ const typeDefs = gql`
colorId: ID
supportSecret: String!
): Item!
setLayerBodyId(
layerId: ID!
bodyId: ID!
supportSecret: String!
): AppearanceLayer!
}
`;
@ -288,11 +299,18 @@ const resolvers = {
},
},
AppearanceLayer: {
zone: async (layer, _, { zoneLoader }) => {
bodyId: async ({ id }, _, { swfAssetLoader }) => {
const layer = await swfAssetLoader.load(id);
return layer.bodyId;
},
zone: async ({ id }, _, { swfAssetLoader, zoneLoader }) => {
const layer = await swfAssetLoader.load(id);
const zone = await zoneLoader.load(layer.zoneId);
return zone;
},
imageUrl: (layer, { size }) => {
imageUrl: async ({ id }, { size }, { swfAssetLoader }) => {
const layer = await swfAssetLoader.load(id);
if (!layer.hasImage) {
return null;
}
@ -311,7 +329,9 @@ const resolvers = {
`/${rid1}/${rid2}/${rid3}/${rid}/${sizeNum}x${sizeNum}.png?v2-${time}`
);
},
svgUrl: async (layer, _, { svgLogger }) => {
svgUrl: async ({ id }, _, { swfAssetLoader, svgLogger }) => {
const layer = await swfAssetLoader.load(id);
const manifest = await neopets.loadAssetManifest(layer.url);
if (!manifest) {
svgLogger.log("no-manifest");
@ -339,6 +359,24 @@ const resolvers = {
const url = new URL(assetDatum.path, "http://images.neopets.com");
return url.toString();
},
item: async ({ id }, _, { db }) => {
// TODO: If this becomes a popular request, we'll definitely need to
// loaderize this! I'm cheating for now because it's just Support, one at
// a time.
const [rows] = await db.query(
`
SELECT parent_id FROM parents_swf_assets
WHERE swf_asset_id = ? AND parent_type = "Item" LIMIT 1;
`,
[id]
);
if (rows.length === 0) {
return null;
}
return { id: String(rows[0].parent_id) };
},
},
Zone: {
depth: async ({ id }, _, { zoneLoader }) => {
@ -515,6 +553,27 @@ const resolvers = {
return { id: itemId };
},
setLayerBodyId: async (_, { layerId, bodyId, supportSecret }, { db }) => {
if (supportSecret !== process.env["SUPPORT_SECRET"]) {
throw new Error(`Support secret is incorrect. Try setting up again?`);
}
const [
result,
] = await db.execute(
`UPDATE swf_assets SET body_id = ? WHERE id = ? LIMIT 1`,
[bodyId, layerId]
);
if (result.affectedRows !== 1) {
throw new Error(
`Expected to affect 1 layer, but affected ${result.affectedRows}`
);
}
return { id: layerId };
},
},
};

View file

@ -223,7 +223,22 @@ const buildPetTypeBySpeciesAndColorLoader = (db, loaders) =>
);
});
const buildItemSwfAssetLoader = (db) =>
const buildSwfAssetLoader = (db) =>
new DataLoader(async (swfAssetIds) => {
const qs = swfAssetIds.map((_) => "?").join(",");
const [rows, _] = await db.execute(
`SELECT * FROM swf_assets WHERE id IN (${qs})`,
swfAssetIds
);
const entities = rows.map(normalizeRow);
return swfAssetIds.map((swfAssetId) =>
entities.find((e) => e.id === swfAssetId)
);
});
const buildItemSwfAssetLoader = (db, loaders) =>
new DataLoader(async (itemAndBodyPairs) => {
const conditions = [];
const values = [];
@ -245,6 +260,10 @@ const buildItemSwfAssetLoader = (db) =>
const entities = rows.map(normalizeRow);
for (const swfAsset of entities) {
loaders.swfAssetLoader.prime(swfAsset.id, swfAsset);
}
return itemAndBodyPairs.map(({ itemId, bodyId }) =>
entities.filter(
(e) =>
@ -253,7 +272,7 @@ const buildItemSwfAssetLoader = (db) =>
);
});
const buildPetSwfAssetLoader = (db) =>
const buildPetSwfAssetLoader = (db, loaders) =>
new DataLoader(async (petStateIds) => {
const qs = petStateIds.map((_) => "?").join(",");
const [rows, _] = await db.execute(
@ -267,6 +286,10 @@ const buildPetSwfAssetLoader = (db) =>
const entities = rows.map(normalizeRow);
for (const swfAsset of entities) {
loaders.swfAssetLoader.prime(swfAsset.id, swfAsset);
}
return petStateIds.map((petStateId) =>
entities.filter((e) => e.parentId === petStateId)
);
@ -388,8 +411,9 @@ function buildLoaders(db) {
db,
loaders
);
loaders.itemSwfAssetLoader = buildItemSwfAssetLoader(db);
loaders.petSwfAssetLoader = buildPetSwfAssetLoader(db);
loaders.swfAssetLoader = buildSwfAssetLoader(db);
loaders.itemSwfAssetLoader = buildItemSwfAssetLoader(db, loaders);
loaders.petSwfAssetLoader = buildPetSwfAssetLoader(db, loaders);
loaders.outfitLoader = buildOutfitLoader(db);
loaders.itemOutfitRelationshipsLoader = buildItemOutfitRelationshipsLoader(
db