Add support tools for pet layers
This commit is contained in:
parent
e8a2b8ba28
commit
b9b6667414
3 changed files with 210 additions and 80 deletions
|
@ -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,6 +249,8 @@ function AppearanceLayerSupportModal({
|
||||||
SWF <ExternalLinkIcon ml="1" />
|
SWF <ExternalLinkIcon ml="1" />
|
||||||
</Button>
|
</Button>
|
||||||
<Box flex="1 1 0" />
|
<Box flex="1 1 0" />
|
||||||
|
{item && (
|
||||||
|
<>
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
colorScheme="gray"
|
colorScheme="gray"
|
||||||
|
@ -257,10 +264,14 @@ function AppearanceLayerSupportModal({
|
||||||
isOpen={uploadModalIsOpen}
|
isOpen={uploadModalIsOpen}
|
||||||
onClose={() => setUploadModalIsOpen(false)}
|
onClose={() => setUploadModalIsOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
</MetadataValue>
|
</MetadataValue>
|
||||||
</Metadata>
|
</Metadata>
|
||||||
<Box height="8" />
|
<Box height="8" />
|
||||||
|
{item && (
|
||||||
|
<>
|
||||||
<AppearanceLayerSupportPetCompatibilityFields
|
<AppearanceLayerSupportPetCompatibilityFields
|
||||||
item={item}
|
item={item}
|
||||||
layer={layer}
|
layer={layer}
|
||||||
|
@ -271,18 +282,22 @@ function AppearanceLayerSupportModal({
|
||||||
onChangePreviewBiology={setPreviewBiology}
|
onChangePreviewBiology={setPreviewBiology}
|
||||||
/>
|
/>
|
||||||
<Box height="8" />
|
<Box height="8" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<AppearanceLayerSupportKnownGlitchesFields
|
<AppearanceLayerSupportKnownGlitchesFields
|
||||||
selectedKnownGlitches={selectedKnownGlitches}
|
selectedKnownGlitches={selectedKnownGlitches}
|
||||||
onChange={setSelectedKnownGlitches}
|
onChange={setSelectedKnownGlitches}
|
||||||
/>
|
/>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
{item && (
|
||||||
<AppearanceLayerSupportModalRemoveButton
|
<AppearanceLayerSupportModalRemoveButton
|
||||||
item={item}
|
item={item}
|
||||||
layer={layer}
|
layer={layer}
|
||||||
outfitState={outfitState}
|
outfitState={outfitState}
|
||||||
onRemoveSuccess={onClose}
|
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
|
||||||
|
|
|
@ -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>
|
||||||
|
<Wrap spacing="1">
|
||||||
{currentPetAppearance.layers
|
{currentPetAppearance.layers
|
||||||
.map((l) => l.zone)
|
.map((layer) => [`${layer.zone.label} (${layer.zone.id})`, layer])
|
||||||
.map((z) => `${z.label} (${z.id})`)
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
.sort()
|
.map(([text, layer]) => (
|
||||||
.join(", ")}
|
<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,
|
||||||
|
|
|
@ -24,7 +24,7 @@ function getAuth0() {
|
||||||
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,17 +457,18 @@ 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]));
|
||||||
|
|
||||||
|
if (parentType === "Item") {
|
||||||
const [item, itemTranslation, zoneTranslation] = await Promise.all([
|
const [item, itemTranslation, zoneTranslation] = await Promise.all([
|
||||||
itemLoader.load(itemId),
|
itemLoader.load(parentId),
|
||||||
itemTranslationLoader.load(itemId),
|
itemTranslationLoader.load(parentId),
|
||||||
zoneTranslationLoader.load(oldSwfAsset.zoneId),
|
zoneTranslationLoader.load(oldSwfAsset.zoneId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -487,10 +492,56 @@ const resolvers = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
url: `https://impress.openneo.net/items/${itemId}`,
|
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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue