Add support tools for pet layers

This commit is contained in:
Emi Matchu 2021-04-21 22:06:07 -07:00
parent e8a2b8ba28
commit b9b6667414
3 changed files with 210 additions and 80 deletions

View file

@ -41,7 +41,8 @@ import useSupport from "./useSupport";
* appearance layer. Open it by clicking a layer from ItemSupportDrawer. * appearance layer. Open it by clicking a layer from ItemSupportDrawer.
*/ */
function AppearanceLayerSupportModal({ function AppearanceLayerSupportModal({
item, item, // Specify this or `petAppearance`
petAppearance, // Specify this or `item`
layer, layer,
outfitState, // speciesId, colorId, pose outfitState, // speciesId, colorId, pose
isOpen, isOpen,
@ -62,12 +63,16 @@ function AppearanceLayerSupportModal({
const { supportSecret } = useSupport(); const { supportSecret } = useSupport();
const toast = useToast(); const toast = useToast();
const parentName = item
? item.name
: `${petAppearance.color.name} ${petAppearance.species.name} ${petAppearance.id}`;
const [ const [
mutate, mutate,
{ loading: mutationLoading, error: mutationError }, { loading: mutationLoading, error: mutationError },
] = useMutation( ] = useMutation(
gql` gql`
mutation ItemSupportSetLayerBodyId( mutation ApperanceLayerSupportSetLayerBodyId(
$layerId: ID! $layerId: ID!
$bodyId: ID! $bodyId: ID!
$knownGlitches: [AppearanceLayerKnownGlitch!]! $knownGlitches: [AppearanceLayerKnownGlitch!]!
@ -134,7 +139,7 @@ function AppearanceLayerSupportModal({
onClose(); onClose();
toast({ toast({
status: "success", status: "success",
title: `Saved layer ${layer.id}: ${item.name}`, title: `Saved layer ${layer.id}: ${parentName}`,
}); });
}, },
} }
@ -153,7 +158,7 @@ function AppearanceLayerSupportModal({
<ModalOverlay> <ModalOverlay>
<ModalContent> <ModalContent>
<ModalHeader> <ModalHeader>
Layer {layer.id}: {item.name} Layer {layer.id}: {parentName}
</ModalHeader> </ModalHeader>
<ModalCloseButton /> <ModalCloseButton />
<ModalBody> <ModalBody>
@ -244,45 +249,55 @@ function AppearanceLayerSupportModal({
SWF <ExternalLinkIcon ml="1" /> SWF <ExternalLinkIcon ml="1" />
</Button> </Button>
<Box flex="1 1 0" /> <Box flex="1 1 0" />
<Button {item && (
size="xs" <>
colorScheme="gray" <Button
onClick={() => setUploadModalIsOpen(true)} size="xs"
> colorScheme="gray"
Upload PNG <ChevronRightIcon /> onClick={() => setUploadModalIsOpen(true)}
</Button> >
<AppearanceLayerSupportUploadModal Upload PNG <ChevronRightIcon />
item={item} </Button>
layer={layer} <AppearanceLayerSupportUploadModal
isOpen={uploadModalIsOpen} item={item}
onClose={() => setUploadModalIsOpen(false)} layer={layer}
/> isOpen={uploadModalIsOpen}
onClose={() => setUploadModalIsOpen(false)}
/>
</>
)}
</HStack> </HStack>
</MetadataValue> </MetadataValue>
</Metadata> </Metadata>
<Box height="8" /> <Box height="8" />
<AppearanceLayerSupportPetCompatibilityFields {item && (
item={item} <>
layer={layer} <AppearanceLayerSupportPetCompatibilityFields
outfitState={outfitState} item={item}
selectedBodyId={selectedBodyId} layer={layer}
previewBiology={previewBiology} outfitState={outfitState}
onChangeBodyId={setSelectedBodyId} selectedBodyId={selectedBodyId}
onChangePreviewBiology={setPreviewBiology} previewBiology={previewBiology}
/> onChangeBodyId={setSelectedBodyId}
<Box height="8" /> onChangePreviewBiology={setPreviewBiology}
/>
<Box height="8" />
</>
)}
<AppearanceLayerSupportKnownGlitchesFields <AppearanceLayerSupportKnownGlitchesFields
selectedKnownGlitches={selectedKnownGlitches} selectedKnownGlitches={selectedKnownGlitches}
onChange={setSelectedKnownGlitches} onChange={setSelectedKnownGlitches}
/> />
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<AppearanceLayerSupportModalRemoveButton {item && (
item={item} <AppearanceLayerSupportModalRemoveButton
layer={layer} item={item}
outfitState={outfitState} layer={layer}
onRemoveSuccess={onClose} outfitState={outfitState}
/> onRemoveSuccess={onClose}
/>
)}
<Box flex="1 0 0" /> <Box flex="1 0 0" />
{mutationError && ( {mutationError && (
<Box <Box
@ -298,7 +313,11 @@ function AppearanceLayerSupportModal({
<Button <Button
isLoading={mutationLoading} isLoading={mutationLoading}
colorScheme="green" colorScheme="green"
onClick={mutate} onClick={() =>
mutate().catch((e) => {
/* Discard errors here; we'll show them in the UI! */
})
}
flex="0 0 auto" flex="0 0 auto"
> >
Save changes Save changes

View file

@ -1,16 +1,28 @@
import React from "react"; import React from "react";
import gql from "graphql-tag"; import gql from "graphql-tag";
import { useMutation, useQuery } from "@apollo/client"; import { useMutation, useQuery } from "@apollo/client";
import { Box, IconButton, Select, Spinner, Switch } from "@chakra-ui/react"; import {
Box,
Button,
IconButton,
Select,
Spinner,
Switch,
Wrap,
WrapItem,
useDisclosure,
} from "@chakra-ui/react";
import { import {
ArrowBackIcon, ArrowBackIcon,
ArrowForwardIcon, ArrowForwardIcon,
CheckCircleIcon, CheckCircleIcon,
EditIcon,
} from "@chakra-ui/icons"; } from "@chakra-ui/icons";
import HangerSpinner from "../../components/HangerSpinner"; import HangerSpinner from "../../components/HangerSpinner";
import Metadata, { MetadataLabel, MetadataValue } from "./Metadata"; import Metadata, { MetadataLabel, MetadataValue } from "./Metadata";
import useSupport from "./useSupport"; import useSupport from "./useSupport";
import AppearanceLayerSupportModal from "./AppearanceLayerSupportModal";
function PosePickerSupport({ function PosePickerSupport({
speciesId, speciesId,
@ -33,6 +45,24 @@ function PosePickerSupport({
id id
label @client label @client
} }
# For AppearanceLayerSupportModal
remoteId
bodyId
swfUrl
svgUrl
imageUrl
canvasMovieLibraryUrl
}
# For AppearanceLayerSupportModal to know the name
species {
id
name
}
color {
id
name
} }
} }
@ -128,19 +158,49 @@ function PosePickerSupport({
colorId={colorId} colorId={colorId}
/> />
</MetadataValue> </MetadataValue>
<MetadataLabel>Zones:</MetadataLabel> <MetadataLabel>Layers:</MetadataLabel>
<MetadataValue> <MetadataValue>
{currentPetAppearance.layers <Wrap spacing="1">
.map((l) => l.zone) {currentPetAppearance.layers
.map((z) => `${z.label} (${z.id})`) .map((layer) => [`${layer.zone.label} (${layer.zone.id})`, layer])
.sort() .sort((a, b) => a[0].localeCompare(b[0]))
.join(", ")} .map(([text, layer]) => (
<WrapItem>
<PetLayerSupportLink
outfitState={{ speciesId, colorId, pose }}
petAppearance={currentPetAppearance}
layer={layer}
>
{text}
<EditIcon marginLeft="1" />
</PetLayerSupportLink>
</WrapItem>
))}
</Wrap>
</MetadataValue> </MetadataValue>
</Metadata> </Metadata>
</Box> </Box>
); );
} }
function PetLayerSupportLink({ outfitState, petAppearance, layer, children }) {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<Button size="xs" onClick={onOpen}>
{children}
</Button>
<AppearanceLayerSupportModal
outfitState={outfitState}
petAppearance={petAppearance}
layer={layer}
isOpen={isOpen}
onClose={onClose}
/>
</>
);
}
function PosePickerSupportNavigator({ function PosePickerSupportNavigator({
petAppearances, petAppearances,
currentPetAppearance, currentPetAppearance,

View file

@ -18,13 +18,13 @@ let auth0;
*/ */
function getAuth0() { function getAuth0() {
if (!auth0) { if (!auth0) {
auth0 = new ManagementClient({ auth0 = new ManagementClient({
domain: "openneo.us.auth0.com", domain: "openneo.us.auth0.com",
clientId: process.env.AUTH0_SUPPORT_CLIENT_ID, clientId: process.env.AUTH0_SUPPORT_CLIENT_ID,
clientSecret: process.env.AUTH0_SUPPORT_CLIENT_SECRET, clientSecret: process.env.AUTH0_SUPPORT_CLIENT_SECRET,
scope: "read:users update:users", scope: "read:users update:users",
}); });
} }
return auth0; return auth0;
} }
@ -412,6 +412,10 @@ const resolvers = {
itemTranslationLoader, itemTranslationLoader,
swfAssetLoader, swfAssetLoader,
zoneTranslationLoader, zoneTranslationLoader,
petStateLoader,
petTypeLoader,
colorTranslationLoader,
speciesTranslationLoader,
db, db,
} }
) => { ) => {
@ -453,44 +457,91 @@ const resolvers = {
if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) { if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) {
try { try {
const itemId = await db const { parentType, parentId } = await db
.execute( .execute(
`SELECT parent_id FROM parents_swf_assets `SELECT parent_type, parent_id FROM parents_swf_assets
WHERE swf_asset_id = ? AND parent_type = "Item" LIMIT 1;`, WHERE swf_asset_id = ? LIMIT 1;`,
[layerId] [layerId]
) )
.then(([rows]) => normalizeRow(rows[0]).parentId); .then(([rows]) => normalizeRow(rows[0]));
const [item, itemTranslation, zoneTranslation] = await Promise.all([ if (parentType === "Item") {
itemLoader.load(itemId), const [item, itemTranslation, zoneTranslation] = await Promise.all([
itemTranslationLoader.load(itemId), itemLoader.load(parentId),
zoneTranslationLoader.load(oldSwfAsset.zoneId), itemTranslationLoader.load(parentId),
]); zoneTranslationLoader.load(oldSwfAsset.zoneId),
]);
await logToDiscord({ await logToDiscord({
embeds: [ embeds: [
{ {
title: `🛠 ${itemTranslation.name}`, title: `🛠 ${itemTranslation.name}`,
thumbnail: { thumbnail: {
url: item.thumbnailUrl, url: item.thumbnailUrl,
height: 80, height: 80,
width: 80, width: 80,
},
fields: [
{
name:
`Layer ${layerId} (${zoneTranslation.label}): ` +
`Known glitches`,
value: `${oldSwfAsset.knownGlitches || "<none>"} → **${
newKnownGlitchesString || "<none>"
}**`,
}, },
], fields: [
timestamp: new Date().toISOString(), {
url: `https://impress.openneo.net/items/${itemId}`, name:
}, `Layer ${layerId} (${zoneTranslation.label}): ` +
], `Known glitches`,
}); value: `${oldSwfAsset.knownGlitches || "<none>"} → **${
newKnownGlitchesString || "<none>"
}**`,
},
],
timestamp: new Date().toISOString(),
url: `https://impress.openneo.net/items/${parentId}`,
},
],
});
} else if (parentType === "PetState") {
const petState = await petStateLoader.load(parentId);
const petType = await petTypeLoader.load(petState.petTypeId);
const [
colorTranslation,
speciesTranslation,
zoneTranslation,
] = await Promise.all([
colorTranslationLoader.load(petType.colorId),
speciesTranslationLoader.load(petType.speciesId),
zoneTranslationLoader.load(oldSwfAsset.zoneId),
]);
const colorName = capitalize(colorTranslation.name);
const speciesName = capitalize(speciesTranslation.name);
const pose = getPoseFromPetState(petState);
await logToDiscord({
embeds: [
{
title: `🛠 ${colorName} ${speciesName}`,
thumbnail: {
url: `http://pets.neopets.com/cp/${
petType.basicImageHash || petType.imageHash
}/1/6.png`,
height: 150,
width: 150,
},
fields: [
{
name: `Appearance ${petState.id}, Layer ${layerId} (${zoneTranslation.label}): Known glitches`,
value: `${oldSwfAsset.knownGlitches || "<none>"} → **${
newKnownGlitchesString || "<none>"
}**`,
},
],
timestamp: new Date().toISOString(),
url: `https://impress-2020.openneo.net/outfits/new?species=${petType.speciesId}&color=${petType.colorId}&pose=${pose}&state=${petState.id}`,
},
],
});
} else {
console.error(
`Error building Discord support log: unexpected parent type ${parentType}`
);
}
} catch (e) { } catch (e) {
console.error("Error sending Discord support log", e); console.error("Error sending Discord support log", e);
} }