can submit the actual body ID Support mutation!
it seems to actually be changing the things correctly aaaa
This commit is contained in:
parent
e8917936d6
commit
8fdc986ee9
4 changed files with 211 additions and 28 deletions
|
@ -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 });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue