refactor e/gp pairs to pose enum
This commit is contained in:
parent
772917fde6
commit
75a0fe2e8c
13 changed files with 210 additions and 182 deletions
|
@ -128,8 +128,7 @@ function SubmitPetForm() {
|
||||||
name: petName,
|
name: petName,
|
||||||
species: species.id,
|
species: species.id,
|
||||||
color: color.id,
|
color: color.id,
|
||||||
emotion: "HAPPY", // TODO: Ask PetService
|
pose: "HAPPY_FEM", // TODO: Ask PetService
|
||||||
genderPresentation: "FEMININE", // TODO: Ask PetService
|
|
||||||
});
|
});
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
params.append("objects[]", item.id);
|
params.append("objects[]", item.id);
|
||||||
|
|
|
@ -32,7 +32,7 @@ function PosePicker({
|
||||||
}) {
|
}) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const checkedInputRef = React.useRef();
|
const checkedInputRef = React.useRef();
|
||||||
const { loading, error, poses } = usePoses(outfitState);
|
const { loading, error, poseInfos } = usePoses(outfitState);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -45,19 +45,15 @@ function PosePicker({
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's only one pose anyway, don't bother showing a picker!
|
// If there's only one pose anyway, don't bother showing a picker!
|
||||||
const numAvailablePoses = Object.values(poses).filter((p) => p.isAvailable)
|
const numAvailablePoses = Object.values(poseInfos).filter(
|
||||||
.length;
|
(p) => p.isAvailable
|
||||||
|
).length;
|
||||||
if (numAvailablePoses <= 1) {
|
if (numAvailablePoses <= 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = (e) => {
|
const onChange = (e) => {
|
||||||
const [emotion, genderPresentation] = e.target.value.split("-");
|
dispatchToOutfit({ type: "setPose", pose: e.target.value });
|
||||||
dispatchToOutfit({
|
|
||||||
type: "setPose",
|
|
||||||
emotion,
|
|
||||||
genderPresentation,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -99,13 +95,13 @@ function PosePicker({
|
||||||
isOpen && "is-open"
|
isOpen && "is-open"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{outfitState.emotion === "HAPPY" && (
|
{getEmotion(outfitState.pose) === "HAPPY" && (
|
||||||
<EmojiImage src={twemojiSmile} alt="Choose a pose" />
|
<EmojiImage src={twemojiSmile} alt="Choose a pose" />
|
||||||
)}
|
)}
|
||||||
{outfitState.emotion === "SAD" && (
|
{getEmotion(outfitState.pose) === "SAD" && (
|
||||||
<EmojiImage src={twemojiCry} alt="Choose a pose" />
|
<EmojiImage src={twemojiCry} alt="Choose a pose" />
|
||||||
)}
|
)}
|
||||||
{outfitState.emotion === "SICK" && (
|
{getEmotion(outfitState.pose) === "SICK" && (
|
||||||
<EmojiImage src={twemojiSick} alt="Choose a pose" />
|
<EmojiImage src={twemojiSick} alt="Choose a pose" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -133,24 +129,30 @@ function PosePicker({
|
||||||
<EmojiImage src={twemojiMasc} alt="Masculine" />
|
<EmojiImage src={twemojiMasc} alt="Masculine" />
|
||||||
</Cell>
|
</Cell>
|
||||||
<Cell as="td">
|
<Cell as="td">
|
||||||
<PoseButton
|
<PoseOption
|
||||||
pose={poses.happyMasc}
|
poseInfo={poseInfos.happyMasc}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
inputRef={poses.happyMasc.isSelected && checkedInputRef}
|
inputRef={
|
||||||
|
poseInfos.happyMasc.isSelected && checkedInputRef
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Cell>
|
</Cell>
|
||||||
<Cell as="td">
|
<Cell as="td">
|
||||||
<PoseButton
|
<PoseOption
|
||||||
pose={poses.sadMasc}
|
poseInfo={poseInfos.sadMasc}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
inputRef={poses.sadMasc.isSelected && checkedInputRef}
|
inputRef={
|
||||||
|
poseInfos.sadMasc.isSelected && checkedInputRef
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Cell>
|
</Cell>
|
||||||
<Cell as="td">
|
<Cell as="td">
|
||||||
<PoseButton
|
<PoseOption
|
||||||
pose={poses.sickMasc}
|
poseInfo={poseInfos.sickMasc}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
inputRef={poses.sickMasc.isSelected && checkedInputRef}
|
inputRef={
|
||||||
|
poseInfos.sickMasc.isSelected && checkedInputRef
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Cell>
|
</Cell>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -159,24 +161,30 @@ function PosePicker({
|
||||||
<EmojiImage src={twemojiFem} alt="Feminine" />
|
<EmojiImage src={twemojiFem} alt="Feminine" />
|
||||||
</Cell>
|
</Cell>
|
||||||
<Cell as="td">
|
<Cell as="td">
|
||||||
<PoseButton
|
<PoseOption
|
||||||
pose={poses.happyFem}
|
poseInfo={poseInfos.happyFem}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
inputRef={poses.happyFem.isSelected && checkedInputRef}
|
inputRef={
|
||||||
|
poseInfos.happyFem.isSelected && checkedInputRef
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Cell>
|
</Cell>
|
||||||
<Cell as="td">
|
<Cell as="td">
|
||||||
<PoseButton
|
<PoseOption
|
||||||
pose={poses.sadFem}
|
poseInfo={poseInfos.sadFem}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
inputRef={poses.sadFem.isSelected && checkedInputRef}
|
inputRef={
|
||||||
|
poseInfos.sadFem.isSelected && checkedInputRef
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Cell>
|
</Cell>
|
||||||
<Cell as="td">
|
<Cell as="td">
|
||||||
<PoseButton
|
<PoseOption
|
||||||
pose={poses.sickFem}
|
poseInfo={poseInfos.sickFem}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
inputRef={poses.sickFem.isSelected && checkedInputRef}
|
inputRef={
|
||||||
|
poseInfos.sickFem.isSelected && checkedInputRef
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Cell>
|
</Cell>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -213,14 +221,14 @@ const GENDER_PRESENTATION_STRINGS = {
|
||||||
FEMININE: "Feminine",
|
FEMININE: "Feminine",
|
||||||
};
|
};
|
||||||
|
|
||||||
function PoseButton({ pose, onChange, inputRef }) {
|
function PoseOption({ poseInfo, onChange, inputRef }) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const genderPresentationStr =
|
const genderPresentationStr =
|
||||||
GENDER_PRESENTATION_STRINGS[pose.genderPresentation];
|
GENDER_PRESENTATION_STRINGS[poseInfo.genderPresentation];
|
||||||
const emotionStr = EMOTION_STRINGS[pose.emotion];
|
const emotionStr = EMOTION_STRINGS[poseInfo.emotion];
|
||||||
|
|
||||||
let label = `${emotionStr} and ${genderPresentationStr}`;
|
let label = `${emotionStr} and ${genderPresentationStr}`;
|
||||||
if (!pose.isAvailable) {
|
if (!poseInfo.isAvailable) {
|
||||||
label += ` (not modeled yet)`;
|
label += ` (not modeled yet)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,9 +247,9 @@ function PoseButton({ pose, onChange, inputRef }) {
|
||||||
type="radio"
|
type="radio"
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
name="pose"
|
name="pose"
|
||||||
value={`${pose.emotion}-${pose.genderPresentation}`}
|
value={poseInfo.pose}
|
||||||
checked={pose.isSelected}
|
checked={poseInfo.isSelected}
|
||||||
disabled={!pose.isAvailable}
|
disabled={!poseInfo.isAvailable}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
ref={inputRef || null}
|
ref={inputRef || null}
|
||||||
/>
|
/>
|
||||||
|
@ -253,11 +261,11 @@ function PoseButton({ pose, onChange, inputRef }) {
|
||||||
width="50px"
|
width="50px"
|
||||||
height="50px"
|
height="50px"
|
||||||
title={
|
title={
|
||||||
pose.isAvailable
|
poseInfo.isAvailable
|
||||||
? // A lil debug output, so that we can quickly identify glitched
|
? // A lil debug output, so that we can quickly identify glitched
|
||||||
// PetStates and manually mark them as glitched!
|
// PetStates and manually mark them as glitched!
|
||||||
window.location.hostname.includes("localhost") &&
|
window.location.hostname.includes("localhost") &&
|
||||||
`#${pose.petStateId}`
|
`#${poseInfo.petStateId}`
|
||||||
: "Not modeled yet"
|
: "Not modeled yet"
|
||||||
}
|
}
|
||||||
position="relative"
|
position="relative"
|
||||||
|
@ -298,18 +306,18 @@ function PoseButton({ pose, onChange, inputRef }) {
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
!pose.isAvailable && "not-available"
|
!poseInfo.isAvailable && "not-available"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{pose.isAvailable ? (
|
{poseInfo.isAvailable ? (
|
||||||
<Box
|
<Box
|
||||||
width="50px"
|
width="50px"
|
||||||
height="50px"
|
height="50px"
|
||||||
transform={
|
transform={
|
||||||
transformsByBodyId[pose.bodyId] || transformsByBodyId.default
|
transformsByBodyId[poseInfo.bodyId] || transformsByBodyId.default
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<OutfitLayers visibleLayers={getVisibleLayers(pose, [])} />
|
<OutfitLayers visibleLayers={getVisibleLayers(poseInfo, [])} />
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Flex align="center" justify="center">
|
<Flex align="center" justify="center">
|
||||||
|
@ -342,8 +350,7 @@ function usePoses(outfitState) {
|
||||||
id
|
id
|
||||||
petStateId
|
petStateId
|
||||||
bodyId
|
bodyId
|
||||||
genderPresentation
|
pose
|
||||||
emotion
|
|
||||||
approximateThumbnailUrl
|
approximateThumbnailUrl
|
||||||
...PetAppearanceForOutfitPreview
|
...PetAppearanceForOutfitPreview
|
||||||
}
|
}
|
||||||
|
@ -354,28 +361,39 @@ function usePoses(outfitState) {
|
||||||
);
|
);
|
||||||
|
|
||||||
const petAppearances = data?.petAppearances || [];
|
const petAppearances = data?.petAppearances || [];
|
||||||
const buildPose = (e, gp) => {
|
const buildPoseInfo = (pose) => {
|
||||||
const appearance = petAppearances.find(
|
const appearance = petAppearances.find((pa) => pa.pose === pose);
|
||||||
(pa) => pa.emotion === e && pa.genderPresentation === gp
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
...appearance,
|
...appearance,
|
||||||
isAvailable: Boolean(appearance),
|
isAvailable: Boolean(appearance),
|
||||||
isSelected:
|
isSelected: outfitState.pose === pose,
|
||||||
outfitState.emotion === e && outfitState.genderPresentation === gp,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const poses = {
|
const poseInfos = {
|
||||||
happyMasc: buildPose("HAPPY", "MASCULINE"),
|
happyMasc: buildPoseInfo("HAPPY_MASC"),
|
||||||
sadMasc: buildPose("SAD", "MASCULINE"),
|
sadMasc: buildPoseInfo("SAD_MASC"),
|
||||||
sickMasc: buildPose("SICK", "MASCULINE"),
|
sickMasc: buildPoseInfo("SICK_MASC"),
|
||||||
happyFem: buildPose("HAPPY", "FEMININE"),
|
happyFem: buildPoseInfo("HAPPY_FEM"),
|
||||||
sadFem: buildPose("SAD", "FEMININE"),
|
sadFem: buildPoseInfo("SAD_FEM"),
|
||||||
sickFem: buildPose("SICK", "FEMININE"),
|
sickFem: buildPoseInfo("SICK_FEM"),
|
||||||
};
|
};
|
||||||
|
|
||||||
return { loading, error, poses };
|
return { loading, error, poseInfos };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEmotion(pose) {
|
||||||
|
if (["HAPPY_MASC", "HAPPY_FEM"].includes(pose)) {
|
||||||
|
return "HAPPY";
|
||||||
|
} else if (["SAD_MASC", "SAD_FEM"].includes(pose)) {
|
||||||
|
return "SAD";
|
||||||
|
} else if (["SICK_MASC", "SICK_FEM"].includes(pose)) {
|
||||||
|
return "SICK";
|
||||||
|
} else if (["UNCONVERTED", "UNKNOWN"].includes(pose)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
throw new Error(`unrecognized pose ${JSON.stringify(pose)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const transformsByBodyId = {
|
const transformsByBodyId = {
|
||||||
|
|
|
@ -16,8 +16,8 @@ const cacheRedirects = {
|
||||||
// way, when you switch pet poses, Apollo knows it already has the
|
// way, when you switch pet poses, Apollo knows it already has the
|
||||||
// appearance data and doesn't need to ask the server again!
|
// appearance data and doesn't need to ask the server again!
|
||||||
petAppearance: (_, args, { getCacheKey }) => {
|
petAppearance: (_, args, { getCacheKey }) => {
|
||||||
const { speciesId, colorId, emotion, genderPresentation } = args;
|
const { speciesId, colorId, pose } = args;
|
||||||
const id = `${speciesId}-${colorId}-${emotion}-${genderPresentation}`;
|
const id = `${speciesId}-${colorId}-${pose}`;
|
||||||
return getCacheKey({ __typename: "PetAppearance", id });
|
return getCacheKey({ __typename: "PetAppearance", id });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,13 +6,7 @@ import { useQuery } from "@apollo/react-hooks";
|
||||||
* visibleLayers for rendering.
|
* visibleLayers for rendering.
|
||||||
*/
|
*/
|
||||||
export default function useOutfitAppearance(outfitState) {
|
export default function useOutfitAppearance(outfitState) {
|
||||||
const {
|
const { wornItemIds, speciesId, colorId, pose } = outfitState;
|
||||||
wornItemIds,
|
|
||||||
speciesId,
|
|
||||||
colorId,
|
|
||||||
emotion,
|
|
||||||
genderPresentation,
|
|
||||||
} = outfitState;
|
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(
|
const { loading, error, data } = useQuery(
|
||||||
gql`
|
gql`
|
||||||
|
@ -20,15 +14,9 @@ export default function useOutfitAppearance(outfitState) {
|
||||||
$wornItemIds: [ID!]!
|
$wornItemIds: [ID!]!
|
||||||
$speciesId: ID!
|
$speciesId: ID!
|
||||||
$colorId: ID!
|
$colorId: ID!
|
||||||
$emotion: Emotion!
|
$pose: Pose!
|
||||||
$genderPresentation: GenderPresentation!
|
|
||||||
) {
|
) {
|
||||||
petAppearance(
|
petAppearance(speciesId: $speciesId, colorId: $colorId, pose: $pose) {
|
||||||
speciesId: $speciesId
|
|
||||||
colorId: $colorId
|
|
||||||
emotion: $emotion
|
|
||||||
genderPresentation: $genderPresentation
|
|
||||||
) {
|
|
||||||
...PetAppearanceForOutfitPreview
|
...PetAppearanceForOutfitPreview
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,8 +35,7 @@ export default function useOutfitAppearance(outfitState) {
|
||||||
wornItemIds,
|
wornItemIds,
|
||||||
speciesId,
|
speciesId,
|
||||||
colorId,
|
colorId,
|
||||||
emotion,
|
pose,
|
||||||
genderPresentation,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,7 +15,7 @@ function useOutfitState() {
|
||||||
initialState
|
initialState
|
||||||
);
|
);
|
||||||
|
|
||||||
const { name, speciesId, colorId, emotion, genderPresentation } = state;
|
const { name, speciesId, colorId, pose } = state;
|
||||||
|
|
||||||
// It's more convenient to manage these as a Set in state, but most callers
|
// It's more convenient to manage these as a Set in state, but most callers
|
||||||
// will find it more convenient to access them as arrays! e.g. for `.map()`
|
// will find it more convenient to access them as arrays! e.g. for `.map()`
|
||||||
|
@ -84,8 +84,7 @@ function useOutfitState() {
|
||||||
allItemIds,
|
allItemIds,
|
||||||
speciesId,
|
speciesId,
|
||||||
colorId,
|
colorId,
|
||||||
emotion,
|
pose,
|
||||||
genderPresentation,
|
|
||||||
url,
|
url,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -159,28 +158,21 @@ const outfitStateReducer = (apolloClient) => (baseState, action) => {
|
||||||
closetedItemIds.delete(itemId);
|
closetedItemIds.delete(itemId);
|
||||||
});
|
});
|
||||||
case "setPose":
|
case "setPose":
|
||||||
return produce(baseState, (state) => {
|
return { ...baseState, pose: action.pose };
|
||||||
const { emotion, genderPresentation } = action;
|
|
||||||
state.emotion = emotion;
|
|
||||||
state.genderPresentation = genderPresentation;
|
|
||||||
});
|
|
||||||
case "reset":
|
case "reset":
|
||||||
return produce(baseState, (state) => {
|
return produce(baseState, (state) => {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
speciesId,
|
speciesId,
|
||||||
colorId,
|
colorId,
|
||||||
emotion,
|
pose,
|
||||||
genderPresentation,
|
|
||||||
wornItemIds,
|
wornItemIds,
|
||||||
closetedItemIds,
|
closetedItemIds,
|
||||||
} = action;
|
} = action;
|
||||||
state.name = name;
|
state.name = name;
|
||||||
state.speciesId = speciesId ? String(speciesId) : baseState.speciesId;
|
state.speciesId = speciesId ? String(speciesId) : baseState.speciesId;
|
||||||
state.colorId = colorId ? String(colorId) : baseState.colorId;
|
state.colorId = colorId ? String(colorId) : baseState.colorId;
|
||||||
state.emotion = emotion || baseState.emotion;
|
state.pose = pose || baseState.pose;
|
||||||
state.genderPresentation =
|
|
||||||
genderPresentation || baseState.genderPresentation;
|
|
||||||
state.wornItemIds = wornItemIds
|
state.wornItemIds = wornItemIds
|
||||||
? new Set(wornItemIds.map(String))
|
? new Set(wornItemIds.map(String))
|
||||||
: baseState.wornItemIds;
|
: baseState.wornItemIds;
|
||||||
|
@ -199,8 +191,7 @@ function parseOutfitUrl() {
|
||||||
name: urlParams.get("name"),
|
name: urlParams.get("name"),
|
||||||
speciesId: urlParams.get("species"),
|
speciesId: urlParams.get("species"),
|
||||||
colorId: urlParams.get("color"),
|
colorId: urlParams.get("color"),
|
||||||
emotion: urlParams.get("emotion") || "HAPPY",
|
pose: urlParams.get("pose") || "HAPPY_FEM",
|
||||||
genderPresentation: urlParams.get("genderPresentation") || "FEMININE",
|
|
||||||
wornItemIds: new Set(urlParams.getAll("objects[]")),
|
wornItemIds: new Set(urlParams.getAll("objects[]")),
|
||||||
closetedItemIds: new Set(urlParams.getAll("closet[]")),
|
closetedItemIds: new Set(urlParams.getAll("closet[]")),
|
||||||
};
|
};
|
||||||
|
@ -335,8 +326,7 @@ function buildOutfitUrl(state) {
|
||||||
name,
|
name,
|
||||||
speciesId,
|
speciesId,
|
||||||
colorId,
|
colorId,
|
||||||
emotion,
|
pose,
|
||||||
genderPresentation,
|
|
||||||
wornItemIds,
|
wornItemIds,
|
||||||
closetedItemIds,
|
closetedItemIds,
|
||||||
} = state;
|
} = state;
|
||||||
|
@ -345,8 +335,7 @@ function buildOutfitUrl(state) {
|
||||||
name: name || "",
|
name: name || "",
|
||||||
species: speciesId,
|
species: speciesId,
|
||||||
color: colorId,
|
color: colorId,
|
||||||
emotion,
|
pose,
|
||||||
genderPresentation,
|
|
||||||
});
|
});
|
||||||
for (const itemId of wornItemIds) {
|
for (const itemId of wornItemIds) {
|
||||||
params.append("objects[]", itemId);
|
params.append("objects[]", itemId);
|
||||||
|
|
Binary file not shown.
|
@ -1,25 +1,25 @@
|
||||||
import connectToDb from "./db";
|
import connectToDb from "./db";
|
||||||
|
|
||||||
import { getPose } from "./util";
|
import { getPoseFromPetState, normalizeRow } from "./util";
|
||||||
|
|
||||||
export default async function getValidPetPoses() {
|
export default async function getValidPetPoses() {
|
||||||
const db = await connectToDb();
|
const db = await connectToDb();
|
||||||
|
|
||||||
const numSpeciesPromise = getNumSpecies(db);
|
const numSpeciesPromise = getNumSpecies(db);
|
||||||
const numColorsPromise = getNumColors(db);
|
const numColorsPromise = getNumColors(db);
|
||||||
const poseTuplesPromise = getPoseTuples(db);
|
const distinctPetStatesPromise = getDistinctPetStates(db);
|
||||||
|
|
||||||
const [numSpecies, numColors, poseTuples] = await Promise.all([
|
const [numSpecies, numColors, distinctPetStates] = await Promise.all([
|
||||||
numSpeciesPromise,
|
numSpeciesPromise,
|
||||||
numColorsPromise,
|
numColorsPromise,
|
||||||
poseTuplesPromise,
|
distinctPetStatesPromise,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const poseStrs = new Set();
|
const poseStrs = new Set();
|
||||||
for (const poseTuple of poseTuples) {
|
for (const petState of distinctPetStates) {
|
||||||
const { species_id, color_id, mood_id, female, unconverted } = poseTuple;
|
const { speciesId, colorId } = petState;
|
||||||
const pose = getPose(mood_id, female, unconverted);
|
const pose = getPoseFromPetState(petState);
|
||||||
const poseStr = `${species_id}-${color_id}-${pose}`;
|
const poseStr = `${speciesId}-${colorId}-${pose}`;
|
||||||
poseStrs.add(poseStr);
|
poseStrs.add(poseStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,11 +77,11 @@ async function getNumColors(db) {
|
||||||
return rows[0]["count(*)"];
|
return rows[0]["count(*)"];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPoseTuples(db) {
|
async function getDistinctPetStates(db) {
|
||||||
const [rows, _] = await db.query(`
|
const [rows, _] = await db.query(`
|
||||||
SELECT DISTINCT species_id, color_id, mood_id, female, unconverted
|
SELECT DISTINCT species_id, color_id, mood_id, female, unconverted
|
||||||
FROM pet_states
|
FROM pet_states
|
||||||
INNER JOIN pet_types ON pet_types.id = pet_states.pet_type_id
|
INNER JOIN pet_types ON pet_types.id = pet_states.pet_type_id
|
||||||
WHERE glitched IS false AND color_id >= 1`);
|
WHERE glitched IS false AND color_id >= 1`);
|
||||||
return rows;
|
return rows.map(normalizeRow);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,15 @@ import getValidPetPoses from "./getValidPetPoses";
|
||||||
describe("getValidPetPoses", () => {
|
describe("getValidPetPoses", () => {
|
||||||
it("gets them and writes them to a buffer", async () => {
|
it("gets them and writes them to a buffer", async () => {
|
||||||
const buffer = await getValidPetPoses();
|
const buffer = await getValidPetPoses();
|
||||||
expect(buffer.toString()).toMatchSnapshot();
|
expect(asBinaryString(buffer)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function asBinaryString(buffer) {
|
||||||
|
let str = "";
|
||||||
|
for (let i = 0; i < buffer.length; i++) {
|
||||||
|
const byte = buffer.readUInt8(i);
|
||||||
|
str += byte.toString(2).padStart(8, "0") + "\n";
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,12 @@ const { gql } = require("apollo-server");
|
||||||
const connectToDb = require("./db");
|
const connectToDb = require("./db");
|
||||||
const buildLoaders = require("./loaders");
|
const buildLoaders = require("./loaders");
|
||||||
const neopets = require("./neopets");
|
const neopets = require("./neopets");
|
||||||
const { capitalize, getEmotion, getGenderPresentation } = require("./util");
|
const {
|
||||||
|
capitalize,
|
||||||
|
getPoseFromPetState,
|
||||||
|
getEmotion,
|
||||||
|
getGenderPresentation,
|
||||||
|
} = require("./util");
|
||||||
|
|
||||||
const typeDefs = gql`
|
const typeDefs = gql`
|
||||||
enum LayerImageSize {
|
enum LayerImageSize {
|
||||||
|
@ -12,6 +17,20 @@ const typeDefs = gql`
|
||||||
SIZE_150
|
SIZE_150
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The poses a PetAppearance can take!
|
||||||
|
"""
|
||||||
|
enum Pose {
|
||||||
|
HAPPY_MASC
|
||||||
|
SAD_MASC
|
||||||
|
SICK_MASC
|
||||||
|
HAPPY_FEM
|
||||||
|
SAD_FEM
|
||||||
|
SICK_FEM
|
||||||
|
UNCONVERTED
|
||||||
|
UNKNOWN # for when we have the data, but we don't know what it is
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A pet's gender presentation: masculine or feminine.
|
A pet's gender presentation: masculine or feminine.
|
||||||
|
|
||||||
|
@ -50,8 +69,9 @@ const typeDefs = gql`
|
||||||
id: ID!
|
id: ID!
|
||||||
petStateId: ID!
|
petStateId: ID!
|
||||||
bodyId: ID!
|
bodyId: ID!
|
||||||
genderPresentation: GenderPresentation
|
pose: Pose!
|
||||||
emotion: Emotion
|
genderPresentation: GenderPresentation # deprecated
|
||||||
|
emotion: Emotion # deprecated
|
||||||
approximateThumbnailUrl: String!
|
approximateThumbnailUrl: String!
|
||||||
layers: [AppearanceLayer!]!
|
layers: [AppearanceLayer!]!
|
||||||
}
|
}
|
||||||
|
@ -120,12 +140,7 @@ const typeDefs = gql`
|
||||||
offset: Int
|
offset: Int
|
||||||
limit: Int
|
limit: Int
|
||||||
): ItemSearchResult!
|
): ItemSearchResult!
|
||||||
petAppearance(
|
petAppearance(speciesId: ID!, colorId: ID!, pose: Pose!): PetAppearance
|
||||||
speciesId: ID!
|
|
||||||
colorId: ID!
|
|
||||||
emotion: Emotion!
|
|
||||||
genderPresentation: GenderPresentation!
|
|
||||||
): PetAppearance
|
|
||||||
petAppearances(speciesId: ID!, colorId: ID!): [PetAppearance!]!
|
petAppearances(speciesId: ID!, colorId: ID!): [PetAppearance!]!
|
||||||
|
|
||||||
petOnNeopetsDotCom(petName: String!): Outfit
|
petOnNeopetsDotCom(petName: String!): Outfit
|
||||||
|
@ -177,15 +192,15 @@ const resolvers = {
|
||||||
PetAppearance: {
|
PetAppearance: {
|
||||||
id: ({ petType, petState }) => {
|
id: ({ petType, petState }) => {
|
||||||
const { speciesId, colorId } = petType;
|
const { speciesId, colorId } = petType;
|
||||||
const emotion = getEmotion(petState.moodId);
|
const pose = getPoseFromPetState(petState);
|
||||||
const genderPresentation = getGenderPresentation(petState.female);
|
return `${speciesId}-${colorId}-${pose}`;
|
||||||
return `${speciesId}-${colorId}-${emotion}-${genderPresentation}`;
|
|
||||||
},
|
},
|
||||||
petStateId: ({ petState }) => petState.id,
|
petStateId: ({ petState }) => petState.id,
|
||||||
bodyId: ({ petType }) => petType.bodyId,
|
bodyId: ({ petType }) => petType.bodyId,
|
||||||
|
pose: ({ petState }) => getPoseFromPetState(petState),
|
||||||
genderPresentation: ({ petState }) =>
|
genderPresentation: ({ petState }) =>
|
||||||
getGenderPresentation(petState.female),
|
getGenderPresentation(getPoseFromPetState(petState)),
|
||||||
emotion: ({ petState }) => getEmotion(petState.moodId),
|
emotion: ({ petState }) => getEmotion(getPoseFromPetState(petState)),
|
||||||
approximateThumbnailUrl: ({ petType, petState }) => {
|
approximateThumbnailUrl: ({ petType, petState }) => {
|
||||||
return `http://pets.neopets.com/cp/${petType.basicImageHash}/${petState.moodId}/1.png`;
|
return `http://pets.neopets.com/cp/${petType.basicImageHash}/${petState.moodId}/1.png`;
|
||||||
},
|
},
|
||||||
|
@ -309,7 +324,7 @@ const resolvers = {
|
||||||
},
|
},
|
||||||
petAppearance: async (
|
petAppearance: async (
|
||||||
_,
|
_,
|
||||||
{ speciesId, colorId, emotion, genderPresentation },
|
{ speciesId, colorId, pose },
|
||||||
{ petTypeLoader, petStateLoader }
|
{ petTypeLoader, petStateLoader }
|
||||||
) => {
|
) => {
|
||||||
const petType = await petTypeLoader.load({
|
const petType = await petTypeLoader.load({
|
||||||
|
@ -319,11 +334,7 @@ const resolvers = {
|
||||||
|
|
||||||
const petStates = await petStateLoader.load(petType.id);
|
const petStates = await petStateLoader.load(petType.id);
|
||||||
// TODO: This could be optimized into the query condition 🤔
|
// TODO: This could be optimized into the query condition 🤔
|
||||||
const petState = petStates.find(
|
const petState = petStates.find((ps) => getPoseFromPetState(ps) === pose);
|
||||||
(ps) =>
|
|
||||||
getEmotion(ps.moodId) === emotion &&
|
|
||||||
getGenderPresentation(ps.female) === genderPresentation
|
|
||||||
);
|
|
||||||
if (!petState) {
|
if (!petState) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const DataLoader = require("dataloader");
|
const DataLoader = require("dataloader");
|
||||||
|
const { normalizeRow } = require("./util");
|
||||||
|
|
||||||
const loadAllColors = (db) => async () => {
|
const loadAllColors = (db) => async () => {
|
||||||
const [rows, _] = await db.execute(`SELECT * FROM colors WHERE prank = 0`);
|
const [rows, _] = await db.execute(`SELECT * FROM colors WHERE prank = 0`);
|
||||||
|
@ -277,18 +278,6 @@ const buildZoneTranslationLoader = (db) =>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function normalizeRow(row) {
|
|
||||||
const normalizedRow = {};
|
|
||||||
for (let [key, value] of Object.entries(row)) {
|
|
||||||
key = key.replace(/_([a-z])/gi, (m) => m[1].toUpperCase());
|
|
||||||
if ((key === "id" || key.endsWith("Id")) && typeof value === "number") {
|
|
||||||
value = String(value);
|
|
||||||
}
|
|
||||||
normalizedRow[key] = value;
|
|
||||||
}
|
|
||||||
return normalizedRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildLoaders(db) {
|
function buildLoaders(db) {
|
||||||
return {
|
return {
|
||||||
loadAllColors: loadAllColors(db),
|
loadAllColors: loadAllColors(db),
|
||||||
|
|
|
@ -6,12 +6,7 @@ describe("PetAppearance", () => {
|
||||||
const res = await query({
|
const res = await query({
|
||||||
query: gql`
|
query: gql`
|
||||||
query {
|
query {
|
||||||
petAppearance(
|
petAppearance(speciesId: "54", colorId: "75", pose: HAPPY_FEM) {
|
||||||
speciesId: "54"
|
|
||||||
colorId: "75"
|
|
||||||
emotion: HAPPY
|
|
||||||
genderPresentation: FEMININE
|
|
||||||
) {
|
|
||||||
layers {
|
layers {
|
||||||
id
|
id
|
||||||
imageUrl(size: SIZE_600)
|
imageUrl(size: SIZE_600)
|
||||||
|
@ -77,6 +72,7 @@ describe("PetAppearance", () => {
|
||||||
id
|
id
|
||||||
bodyId
|
bodyId
|
||||||
petStateId
|
petStateId
|
||||||
|
pose
|
||||||
genderPresentation
|
genderPresentation
|
||||||
emotion
|
emotion
|
||||||
approximateThumbnailUrl
|
approximateThumbnailUrl
|
||||||
|
|
|
@ -64,8 +64,8 @@ Object {
|
||||||
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/1/1.png",
|
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/1/1.png",
|
||||||
"bodyId": "180",
|
"bodyId": "180",
|
||||||
"emotion": "HAPPY",
|
"emotion": "HAPPY",
|
||||||
"genderPresentation": "FEMININE",
|
"genderPresentation": "MASCULINE",
|
||||||
"id": "54-75-HAPPY-FEMININE",
|
"id": "54-75-HAPPY_FEM",
|
||||||
"layers": Array [
|
"layers": Array [
|
||||||
Object {
|
Object {
|
||||||
"id": "5995",
|
"id": "5995",
|
||||||
|
@ -111,13 +111,14 @@ Object {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"petStateId": "17723",
|
"petStateId": "17723",
|
||||||
|
"pose": "HAPPY_FEM",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/1/1.png",
|
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/1/1.png",
|
||||||
"bodyId": "180",
|
"bodyId": "180",
|
||||||
"emotion": "HAPPY",
|
"emotion": "HAPPY",
|
||||||
"genderPresentation": "MASCULINE",
|
"genderPresentation": "MASCULINE",
|
||||||
"id": "54-75-HAPPY-MASCULINE",
|
"id": "54-75-HAPPY_MASC",
|
||||||
"layers": Array [
|
"layers": Array [
|
||||||
Object {
|
Object {
|
||||||
"id": "5995",
|
"id": "5995",
|
||||||
|
@ -163,13 +164,14 @@ Object {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"petStateId": "17742",
|
"petStateId": "17742",
|
||||||
|
"pose": "HAPPY_MASC",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/4/1.png",
|
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/4/1.png",
|
||||||
"bodyId": "180",
|
"bodyId": "180",
|
||||||
"emotion": "SICK",
|
"emotion": "SICK",
|
||||||
"genderPresentation": "FEMININE",
|
"genderPresentation": "MASCULINE",
|
||||||
"id": "54-75-SICK-FEMININE",
|
"id": "54-75-SICK_FEM",
|
||||||
"layers": Array [
|
"layers": Array [
|
||||||
Object {
|
Object {
|
||||||
"id": "5995",
|
"id": "5995",
|
||||||
|
@ -215,13 +217,14 @@ Object {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"petStateId": "10014",
|
"petStateId": "10014",
|
||||||
|
"pose": "SICK_FEM",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/4/1.png",
|
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/4/1.png",
|
||||||
"bodyId": "180",
|
"bodyId": "180",
|
||||||
"emotion": "SICK",
|
"emotion": "SICK",
|
||||||
"genderPresentation": "MASCULINE",
|
"genderPresentation": "MASCULINE",
|
||||||
"id": "54-75-SICK-MASCULINE",
|
"id": "54-75-SICK_MASC",
|
||||||
"layers": Array [
|
"layers": Array [
|
||||||
Object {
|
Object {
|
||||||
"id": "5995",
|
"id": "5995",
|
||||||
|
@ -267,13 +270,14 @@ Object {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"petStateId": "11089",
|
"petStateId": "11089",
|
||||||
|
"pose": "SICK_MASC",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/2/1.png",
|
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/2/1.png",
|
||||||
"bodyId": "180",
|
"bodyId": "180",
|
||||||
"emotion": "SAD",
|
"emotion": "SAD",
|
||||||
"genderPresentation": "FEMININE",
|
"genderPresentation": "MASCULINE",
|
||||||
"id": "54-75-SAD-FEMININE",
|
"id": "54-75-SAD_FEM",
|
||||||
"layers": Array [
|
"layers": Array [
|
||||||
Object {
|
Object {
|
||||||
"id": "5995",
|
"id": "5995",
|
||||||
|
@ -319,13 +323,14 @@ Object {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"petStateId": "5991",
|
"petStateId": "5991",
|
||||||
|
"pose": "SAD_FEM",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/2/1.png",
|
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/2/1.png",
|
||||||
"bodyId": "180",
|
"bodyId": "180",
|
||||||
"emotion": "SAD",
|
"emotion": "SAD",
|
||||||
"genderPresentation": "MASCULINE",
|
"genderPresentation": "MASCULINE",
|
||||||
"id": "54-75-SAD-MASCULINE",
|
"id": "54-75-SAD_MASC",
|
||||||
"layers": Array [
|
"layers": Array [
|
||||||
Object {
|
Object {
|
||||||
"id": "5995",
|
"id": "5995",
|
||||||
|
@ -371,13 +376,14 @@ Object {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"petStateId": "436",
|
"petStateId": "436",
|
||||||
|
"pose": "SAD_MASC",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/null/1.png",
|
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/null/1.png",
|
||||||
"bodyId": "180",
|
"bodyId": "180",
|
||||||
"emotion": null,
|
"emotion": null,
|
||||||
"genderPresentation": null,
|
"genderPresentation": null,
|
||||||
"id": "54-75-null-null",
|
"id": "54-75-UNKNOWN",
|
||||||
"layers": Array [
|
"layers": Array [
|
||||||
Object {
|
Object {
|
||||||
"id": "5995",
|
"id": "5995",
|
||||||
|
@ -430,13 +436,14 @@ Object {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"petStateId": "2",
|
"petStateId": "2",
|
||||||
|
"pose": "UNKNOWN",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/null/1.png",
|
"approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/null/1.png",
|
||||||
"bodyId": "180",
|
"bodyId": "180",
|
||||||
"emotion": null,
|
"emotion": null,
|
||||||
"genderPresentation": null,
|
"genderPresentation": null,
|
||||||
"id": "54-75-null-null",
|
"id": "54-75-UNKNOWN",
|
||||||
"layers": Array [
|
"layers": Array [
|
||||||
Object {
|
Object {
|
||||||
"id": "5995",
|
"id": "5995",
|
||||||
|
@ -489,6 +496,7 @@ Object {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"petStateId": "4751",
|
"petStateId": "4751",
|
||||||
|
"pose": "UNKNOWN",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,55 +2,77 @@ function capitalize(str) {
|
||||||
return str[0].toUpperCase() + str.slice(1);
|
return str[0].toUpperCase() + str.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEmotion(moodId) {
|
function getEmotion(pose) {
|
||||||
if (String(moodId) === "1") {
|
if (["HAPPY_MASC", "HAPPY_FEM"].includes(pose)) {
|
||||||
return "HAPPY";
|
return "HAPPY";
|
||||||
} else if (String(moodId) === "2") {
|
} else if (["SAD_MASC", "SAD_FEM"].includes(pose)) {
|
||||||
return "SAD";
|
return "SAD";
|
||||||
} else if (String(moodId) === "4") {
|
} else if (["SICK_MASC", "SICK_FEM"].includes(pose)) {
|
||||||
return "SICK";
|
return "SICK";
|
||||||
} else if (moodId === null) {
|
} else if (["UNCONVERTED", "UNKNOWN"].includes(pose)) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`unrecognized moodId ${JSON.stringify(moodId)}`);
|
throw new Error(`unrecognized pose ${JSON.stringify(pose)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGenderPresentation(modelPetWasFemale) {
|
function getGenderPresentation(pose) {
|
||||||
if (String(modelPetWasFemale) === "1") {
|
if (["HAPPY_MASC", "SAD_MASC", "SICK_MASC"].includes(pose)) {
|
||||||
return "FEMININE";
|
|
||||||
} else if (String(modelPetWasFemale) === "0") {
|
|
||||||
return "MASCULINE";
|
return "MASCULINE";
|
||||||
} else {
|
} else if (["HAPPY_FEM", "SAD_FEM", "SICK_FEM"].includes(pose)) {
|
||||||
|
return "MASCULINE";
|
||||||
|
} else if (["UNCONVERTED", "UNKNOWN"].includes(pose)) {
|
||||||
return null;
|
return null;
|
||||||
|
} else {
|
||||||
|
throw new Error(`unrecognized pose ${JSON.stringify(pose)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPose(moodId, modelPetWasFemale, isUnconverted) {
|
function getPoseFromPetState(petState) {
|
||||||
if (isUnconverted) {
|
const { moodId, female, unconverted } = petState;
|
||||||
|
|
||||||
|
if (unconverted) {
|
||||||
return "UNCONVERTED";
|
return "UNCONVERTED";
|
||||||
} else if (moodId == null || modelPetWasFemale == null) {
|
} else if (moodId == null || female == null) {
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
} else if (String(moodId) === "1" && String(modelPetWasFemale) === "0") {
|
} else if (String(moodId) === "1" && String(female) === "0") {
|
||||||
return "HAPPY_MASC";
|
return "HAPPY_MASC";
|
||||||
} else if (String(moodId) === "1" && String(modelPetWasFemale) === "1") {
|
} else if (String(moodId) === "1" && String(female) === "1") {
|
||||||
return "HAPPY_FEM";
|
return "HAPPY_FEM";
|
||||||
} else if (String(moodId) === "2" && String(modelPetWasFemale) === "0") {
|
} else if (String(moodId) === "2" && String(female) === "0") {
|
||||||
return "SAD_MASC";
|
return "SAD_MASC";
|
||||||
} else if (String(moodId) === "2" && String(modelPetWasFemale) === "1") {
|
} else if (String(moodId) === "2" && String(female) === "1") {
|
||||||
return "SAD_FEM";
|
return "SAD_FEM";
|
||||||
} else if (String(moodId) === "4" && String(modelPetWasFemale) === "0") {
|
} else if (String(moodId) === "4" && String(female) === "0") {
|
||||||
return "SICK_MASC";
|
return "SICK_MASC";
|
||||||
} else if (String(moodId) === "4" && String(modelPetWasFemale) === "1") {
|
} else if (String(moodId) === "4" && String(female) === "1") {
|
||||||
return "SICK_FEM";
|
return "SICK_FEM";
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`could not identify pose: ` +
|
`could not identify pose: ` +
|
||||||
`moodId=${moodId}, ` +
|
`moodId=${moodId}, ` +
|
||||||
`modelPetWasFemale=${modelPetWasFemale}, ` +
|
`female=${female}, ` +
|
||||||
`isUnconverted=${isUnconverted}`
|
`unconverted=${unconverted}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { capitalize, getEmotion, getGenderPresentation, getPose };
|
function normalizeRow(row) {
|
||||||
|
const normalizedRow = {};
|
||||||
|
for (let [key, value] of Object.entries(row)) {
|
||||||
|
key = key.replace(/_([a-z])/gi, (m) => m[1].toUpperCase());
|
||||||
|
if ((key === "id" || key.endsWith("Id")) && typeof value === "number") {
|
||||||
|
value = String(value);
|
||||||
|
}
|
||||||
|
normalizedRow[key] = value;
|
||||||
|
}
|
||||||
|
return normalizedRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
capitalize,
|
||||||
|
getEmotion,
|
||||||
|
getGenderPresentation,
|
||||||
|
getPoseFromPetState,
|
||||||
|
normalizeRow,
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue