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

View file

@ -1,16 +1,28 @@
import React from "react";
import gql from "graphql-tag";
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 {
ArrowBackIcon,
ArrowForwardIcon,
CheckCircleIcon,
EditIcon,
} from "@chakra-ui/icons";
import HangerSpinner from "../../components/HangerSpinner";
import Metadata, { MetadataLabel, MetadataValue } from "./Metadata";
import useSupport from "./useSupport";
import AppearanceLayerSupportModal from "./AppearanceLayerSupportModal";
function PosePickerSupport({
speciesId,
@ -33,6 +45,24 @@ function PosePickerSupport({
id
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}
/>
</MetadataValue>
<MetadataLabel>Zones:</MetadataLabel>
<MetadataLabel>Layers:</MetadataLabel>
<MetadataValue>
{currentPetAppearance.layers
.map((l) => l.zone)
.map((z) => `${z.label} (${z.id})`)
.sort()
.join(", ")}
<Wrap spacing="1">
{currentPetAppearance.layers
.map((layer) => [`${layer.zone.label} (${layer.zone.id})`, layer])
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([text, layer]) => (
<WrapItem>
<PetLayerSupportLink
outfitState={{ speciesId, colorId, pose }}
petAppearance={currentPetAppearance}
layer={layer}
>
{text}
<EditIcon marginLeft="1" />
</PetLayerSupportLink>
</WrapItem>
))}
</Wrap>
</MetadataValue>
</Metadata>
</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({
petAppearances,
currentPetAppearance,

View file

@ -18,13 +18,13 @@ let auth0;
*/
function getAuth0() {
if (!auth0) {
auth0 = new ManagementClient({
domain: "openneo.us.auth0.com",
clientId: process.env.AUTH0_SUPPORT_CLIENT_ID,
clientSecret: process.env.AUTH0_SUPPORT_CLIENT_SECRET,
scope: "read:users update:users",
});
}
auth0 = new ManagementClient({
domain: "openneo.us.auth0.com",
clientId: process.env.AUTH0_SUPPORT_CLIENT_ID,
clientSecret: process.env.AUTH0_SUPPORT_CLIENT_SECRET,
scope: "read:users update:users",
});
}
return auth0;
}
@ -412,6 +412,10 @@ const resolvers = {
itemTranslationLoader,
swfAssetLoader,
zoneTranslationLoader,
petStateLoader,
petTypeLoader,
colorTranslationLoader,
speciesTranslationLoader,
db,
}
) => {
@ -453,44 +457,91 @@ const resolvers = {
if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) {
try {
const itemId = await db
const { parentType, parentId } = await db
.execute(
`SELECT parent_id FROM parents_swf_assets
WHERE swf_asset_id = ? AND parent_type = "Item" LIMIT 1;`,
`SELECT parent_type, parent_id FROM parents_swf_assets
WHERE swf_asset_id = ? LIMIT 1;`,
[layerId]
)
.then(([rows]) => normalizeRow(rows[0]).parentId);
.then(([rows]) => normalizeRow(rows[0]));
const [item, itemTranslation, zoneTranslation] = await Promise.all([
itemLoader.load(itemId),
itemTranslationLoader.load(itemId),
zoneTranslationLoader.load(oldSwfAsset.zoneId),
]);
if (parentType === "Item") {
const [item, itemTranslation, zoneTranslation] = await Promise.all([
itemLoader.load(parentId),
itemTranslationLoader.load(parentId),
zoneTranslationLoader.load(oldSwfAsset.zoneId),
]);
await logToDiscord({
embeds: [
{
title: `🛠 ${itemTranslation.name}`,
thumbnail: {
url: item.thumbnailUrl,
height: 80,
width: 80,
},
fields: [
{
name:
`Layer ${layerId} (${zoneTranslation.label}): ` +
`Known glitches`,
value: `${oldSwfAsset.knownGlitches || "<none>"} → **${
newKnownGlitchesString || "<none>"
}**`,
await logToDiscord({
embeds: [
{
title: `🛠 ${itemTranslation.name}`,
thumbnail: {
url: item.thumbnailUrl,
height: 80,
width: 80,
},
],
timestamp: new Date().toISOString(),
url: `https://impress.openneo.net/items/${itemId}`,
},
],
});
fields: [
{
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) {
console.error("Error sending Discord support log", e);
}