diff --git a/src/OutfitPreview.js b/src/OutfitPreview.js
index 8a62619..e5c06d6 100644
--- a/src/OutfitPreview.js
+++ b/src/OutfitPreview.js
@@ -15,6 +15,7 @@ import {
} from "@chakra-ui/core";
import { Delay } from "./util";
+import SpeciesColorPicker from "./SpeciesColorPicker";
import "./OutfitPreview.css";
@@ -35,8 +36,9 @@ export const itemAppearanceFragment = gql`
}
`;
-function OutfitPreview({ outfitState }) {
+function OutfitPreview({ outfitState, dispatchToOutfit }) {
const { wornItemIds, speciesId, colorId } = outfitState;
+ const [hasFocus, setHasFocus] = React.useState(false);
const { loading, error, data } = useQuery(
gql`
@@ -122,45 +124,72 @@ function OutfitPreview({ outfitState }) {
)}
-
-
+
+ setHasFocus(true)}
+ onBlur={() => setHasFocus(false)}
/>
-
+
+
+
+ {
+ prepareDownload();
+ setHasFocus(true);
+ }}
+ onBlur={() => setHasFocus(false)}
+ cursor={!downloadImageUrl && "wait"}
+ variant="unstyled"
+ backgroundColor="gray.600"
+ color="gray.50"
+ boxShadow="md"
+ d="flex"
+ alignItems="center"
+ justifyContent="center"
+ opacity={hasFocus ? 1 : 0}
+ transition="all 0.2s"
+ _groupHover={{
+ opacity: 1,
+ }}
+ _focus={{
+ opacity: 1,
+ backgroundColor: "gray.500",
+ }}
+ _hover={{
+ backgroundColor: "gray.500",
+ }}
+ outline="initial"
+ />
+
+
);
@@ -218,14 +247,14 @@ function useDownloadableImage(visibleLayers) {
const [preparedForLayerIds, setPreparedForLayerIds] = React.useState([]);
const prepareDownload = React.useCallback(async () => {
- setDownloadImageUrl(null);
-
// Skip if the current image URL is already correct for these layers.
const layerIds = visibleLayers.map((l) => l.id);
if (layerIds.join(",") === preparedForLayerIds.join(",")) {
return;
}
+ setDownloadImageUrl(null);
+
const imagePromises = visibleLayers.map(
(layer) =>
new Promise((resolve, reject) => {
diff --git a/src/SpeciesColorPicker.js b/src/SpeciesColorPicker.js
new file mode 100644
index 0000000..bd5b887
--- /dev/null
+++ b/src/SpeciesColorPicker.js
@@ -0,0 +1,144 @@
+import React from "react";
+import gql from "graphql-tag";
+import { useQuery } from "@apollo/react-hooks";
+import { Box, Flex, Select, Text, useToast } from "@chakra-ui/core";
+
+import { Delay } from "./util";
+
+function SpeciesColorPicker({
+ outfitState,
+ dispatchToOutfit,
+ onFocus,
+ onBlur,
+}) {
+ const toast = useToast();
+ const { loading, error, data } = useQuery(gql`
+ query {
+ allSpecies {
+ id
+ name
+ }
+
+ allColors {
+ id
+ name
+ }
+
+ allValidSpeciesColorPairs {
+ species {
+ id
+ }
+ color {
+ id
+ }
+ }
+ }
+ `);
+
+ const allColors = (data && [...data.allColors]) || [];
+ allColors.sort((a, b) => a.name.localeCompare(b.name));
+ const allSpecies = (data && [...data.allSpecies]) || [];
+ allSpecies.sort((a, b) => a.name.localeCompare(b.name));
+ const allValidSpeciesColorPairs = React.useMemo(
+ () =>
+ new Set(
+ ((data && data.allValidSpeciesColorPairs) || []).map(
+ (p) => `${p.species.id},${p.color.id}`
+ )
+ ),
+ [data]
+ );
+
+ if (loading) {
+ return (
+
+
+ Loading species/color dataโฆ
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ Error loading species/color data.
+
+ );
+ }
+
+ const onChangeColor = (e) => {
+ const speciesId = outfitState.speciesId;
+ const colorId = e.target.value;
+ const pair = `${speciesId},${colorId}`;
+ if (allValidSpeciesColorPairs.has(pair)) {
+ dispatchToOutfit({ type: "changeColor", colorId: e.target.value });
+ } else {
+ console.log(pair, Array.from(allValidSpeciesColorPairs));
+ const species = allSpecies.find((s) => s.id === speciesId);
+ const color = allColors.find((c) => c.id === colorId);
+ toast({
+ title: `We haven't seen a ${color.name} ${species.name} before! ๐`,
+ status: "warning",
+ });
+ }
+ };
+
+ const onChangeSpecies = (e) => {
+ const colorId = outfitState.colorId;
+ const speciesId = e.target.value;
+ const pair = `${speciesId},${colorId}`;
+ if (allValidSpeciesColorPairs.has(pair)) {
+ dispatchToOutfit({ type: "changeSpecies", speciesId: e.target.value });
+ } else {
+ console.log(pair, Array.from(allValidSpeciesColorPairs));
+ const species = allSpecies.find((s) => s.id === speciesId);
+ const color = allColors.find((c) => c.id === colorId);
+ toast({
+ title: `We haven't seen a ${color.name} ${species.name} before! ๐`,
+ });
+ }
+ };
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default SpeciesColorPicker;
diff --git a/src/WardrobePage.js b/src/WardrobePage.js
index 2324d0a..cd74be8 100644
--- a/src/WardrobePage.js
+++ b/src/WardrobePage.js
@@ -65,7 +65,10 @@ function WardrobePage() {
width="100%"
>
-
+
diff --git a/src/server/index.js b/src/server/index.js
index 1b5b0dd..1981b7b 100644
--- a/src/server/index.js
+++ b/src/server/index.js
@@ -2,6 +2,7 @@ const { gql } = require("apollo-server");
const connectToDb = require("./db");
const buildLoaders = require("./loaders");
+const { capitalize } = require("./util");
const typeDefs = gql`
enum LayerImageSize {
@@ -140,7 +141,7 @@ const resolvers = {
Color: {
name: async (color, _, { colorTranslationLoader }) => {
const colorTranslation = await colorTranslationLoader.load(color.id);
- return colorTranslation.name;
+ return capitalize(colorTranslation.name);
},
},
Species: {
@@ -148,7 +149,7 @@ const resolvers = {
const speciesTranslation = await speciesTranslationLoader.load(
species.id
);
- return speciesTranslation.name;
+ return capitalize(speciesTranslation.name);
},
},
Query: {
diff --git a/src/server/index.test.js b/src/server/index.test.js
index a1b21b3..df54aab 100644
--- a/src/server/index.test.js
+++ b/src/server/index.test.js
@@ -769,223 +769,223 @@ describe("Species", () => {
"allSpecies": Array [
Object {
"id": "1",
- "name": "acara",
+ "name": "Acara",
},
Object {
"id": "2",
- "name": "aisha",
+ "name": "Aisha",
},
Object {
"id": "3",
- "name": "blumaroo",
+ "name": "Blumaroo",
},
Object {
"id": "4",
- "name": "bori",
+ "name": "Bori",
},
Object {
"id": "5",
- "name": "bruce",
+ "name": "Bruce",
},
Object {
"id": "6",
- "name": "buzz",
+ "name": "Buzz",
},
Object {
"id": "7",
- "name": "chia",
+ "name": "Chia",
},
Object {
"id": "8",
- "name": "chomby",
+ "name": "Chomby",
},
Object {
"id": "9",
- "name": "cybunny",
+ "name": "Cybunny",
},
Object {
"id": "10",
- "name": "draik",
+ "name": "Draik",
},
Object {
"id": "11",
- "name": "elephante",
+ "name": "Elephante",
},
Object {
"id": "12",
- "name": "eyrie",
+ "name": "Eyrie",
},
Object {
"id": "13",
- "name": "flotsam",
+ "name": "Flotsam",
},
Object {
"id": "14",
- "name": "gelert",
+ "name": "Gelert",
},
Object {
"id": "15",
- "name": "gnorbu",
+ "name": "Gnorbu",
},
Object {
"id": "16",
- "name": "grarrl",
+ "name": "Grarrl",
},
Object {
"id": "17",
- "name": "grundo",
+ "name": "Grundo",
},
Object {
"id": "18",
- "name": "hissi",
+ "name": "Hissi",
},
Object {
"id": "19",
- "name": "ixi",
+ "name": "Ixi",
},
Object {
"id": "20",
- "name": "jetsam",
+ "name": "Jetsam",
},
Object {
"id": "21",
- "name": "jubjub",
+ "name": "Jubjub",
},
Object {
"id": "22",
- "name": "kacheek",
+ "name": "Kacheek",
},
Object {
"id": "23",
- "name": "kau",
+ "name": "Kau",
},
Object {
"id": "24",
- "name": "kiko",
+ "name": "Kiko",
},
Object {
"id": "25",
- "name": "koi",
+ "name": "Koi",
},
Object {
"id": "26",
- "name": "korbat",
+ "name": "Korbat",
},
Object {
"id": "27",
- "name": "kougra",
+ "name": "Kougra",
},
Object {
"id": "28",
- "name": "krawk",
+ "name": "Krawk",
},
Object {
"id": "29",
- "name": "kyrii",
+ "name": "Kyrii",
},
Object {
"id": "30",
- "name": "lenny",
+ "name": "Lenny",
},
Object {
"id": "31",
- "name": "lupe",
+ "name": "Lupe",
},
Object {
"id": "32",
- "name": "lutari",
+ "name": "Lutari",
},
Object {
"id": "33",
- "name": "meerca",
+ "name": "Meerca",
},
Object {
"id": "34",
- "name": "moehog",
+ "name": "Moehog",
},
Object {
"id": "35",
- "name": "mynci",
+ "name": "Mynci",
},
Object {
"id": "36",
- "name": "nimmo",
+ "name": "Nimmo",
},
Object {
"id": "37",
- "name": "ogrin",
+ "name": "Ogrin",
},
Object {
"id": "38",
- "name": "peophin",
+ "name": "Peophin",
},
Object {
"id": "39",
- "name": "poogle",
+ "name": "Poogle",
},
Object {
"id": "40",
- "name": "pteri",
+ "name": "Pteri",
},
Object {
"id": "41",
- "name": "quiggle",
+ "name": "Quiggle",
},
Object {
"id": "42",
- "name": "ruki",
+ "name": "Ruki",
},
Object {
"id": "43",
- "name": "scorchio",
+ "name": "Scorchio",
},
Object {
"id": "44",
- "name": "shoyru",
+ "name": "Shoyru",
},
Object {
"id": "45",
- "name": "skeith",
+ "name": "Skeith",
},
Object {
"id": "46",
- "name": "techo",
+ "name": "Techo",
},
Object {
"id": "47",
- "name": "tonu",
+ "name": "Tonu",
},
Object {
"id": "48",
- "name": "tuskaninny",
+ "name": "Tuskaninny",
},
Object {
"id": "49",
- "name": "uni",
+ "name": "Uni",
},
Object {
"id": "50",
- "name": "usul",
+ "name": "Usul",
},
Object {
"id": "51",
- "name": "wocky",
+ "name": "Wocky",
},
Object {
"id": "52",
- "name": "xweetok",
+ "name": "Xweetok",
},
Object {
"id": "53",
- "name": "yurble",
+ "name": "Yurble",
},
Object {
"id": "54",
- "name": "zafara",
+ "name": "Zafara",
},
Object {
"id": "55",
- "name": "vandagyre",
+ "name": "Vandagyre",
},
],
}
@@ -1078,373 +1078,369 @@ describe("Color", () => {
expect(res.data).toMatchInlineSnapshot(`
Object {
"allColors": Array [
- Object {
- "id": "-1",
- "name": "nebula",
- },
Object {
"id": "1",
- "name": "alien",
+ "name": "Alien",
},
Object {
"id": "2",
- "name": "apple",
+ "name": "Apple",
},
Object {
"id": "3",
- "name": "asparagus",
+ "name": "Asparagus",
},
Object {
"id": "4",
- "name": "aubergine",
+ "name": "Aubergine",
},
Object {
"id": "5",
- "name": "avocado",
+ "name": "Avocado",
},
Object {
"id": "6",
- "name": "baby",
+ "name": "Baby",
},
Object {
"id": "7",
- "name": "biscuit",
+ "name": "Biscuit",
},
Object {
"id": "8",
- "name": "blue",
+ "name": "Blue",
},
Object {
"id": "9",
- "name": "blueberry",
+ "name": "Blueberry",
},
Object {
"id": "10",
- "name": "brown",
+ "name": "Brown",
},
Object {
"id": "11",
- "name": "camouflage",
+ "name": "Camouflage",
},
Object {
"id": "12",
- "name": "carrot",
+ "name": "Carrot",
},
Object {
"id": "13",
- "name": "checkered",
+ "name": "Checkered",
},
Object {
"id": "14",
- "name": "chocolate",
+ "name": "Chocolate",
},
Object {
"id": "15",
- "name": "chokato",
+ "name": "Chokato",
},
Object {
"id": "16",
- "name": "christmas",
+ "name": "Christmas",
},
Object {
"id": "17",
- "name": "clay",
+ "name": "Clay",
},
Object {
"id": "18",
- "name": "cloud",
+ "name": "Cloud",
},
Object {
"id": "19",
- "name": "coconut",
+ "name": "Coconut",
},
Object {
"id": "20",
- "name": "custard",
+ "name": "Custard",
},
Object {
"id": "21",
- "name": "darigan",
+ "name": "Darigan",
},
Object {
"id": "22",
- "name": "desert",
+ "name": "Desert",
},
Object {
"id": "23",
- "name": "disco",
+ "name": "Disco",
},
Object {
"id": "24",
- "name": "durian",
+ "name": "Durian",
},
Object {
"id": "25",
- "name": "electric",
+ "name": "Electric",
},
Object {
"id": "26",
- "name": "faerie",
+ "name": "Faerie",
},
Object {
"id": "27",
- "name": "fire",
+ "name": "Fire",
},
Object {
"id": "28",
- "name": "garlic",
+ "name": "Garlic",
},
Object {
"id": "29",
- "name": "ghost",
+ "name": "Ghost",
},
Object {
"id": "30",
- "name": "glowing",
+ "name": "Glowing",
},
Object {
"id": "31",
- "name": "gold",
+ "name": "Gold",
},
Object {
"id": "32",
- "name": "gooseberry",
+ "name": "Gooseberry",
},
Object {
"id": "33",
- "name": "grape",
+ "name": "Grape",
},
Object {
"id": "34",
- "name": "green",
+ "name": "Green",
},
Object {
"id": "35",
- "name": "grey",
+ "name": "Grey",
},
Object {
"id": "36",
- "name": "halloween",
+ "name": "Halloween",
},
Object {
"id": "37",
- "name": "ice",
+ "name": "Ice",
},
Object {
"id": "38",
- "name": "invisible",
+ "name": "Invisible",
},
Object {
"id": "39",
- "name": "island",
+ "name": "Island",
},
Object {
"id": "40",
- "name": "jelly",
+ "name": "Jelly",
},
Object {
"id": "41",
- "name": "lemon",
+ "name": "Lemon",
},
Object {
"id": "42",
- "name": "lime",
+ "name": "Lime",
},
Object {
"id": "43",
- "name": "mallow",
+ "name": "Mallow",
},
Object {
"id": "44",
- "name": "maraquan",
+ "name": "Maraquan",
},
Object {
"id": "45",
- "name": "msp",
+ "name": "Msp",
},
Object {
"id": "46",
- "name": "mutant",
+ "name": "Mutant",
},
Object {
"id": "47",
- "name": "orange",
+ "name": "Orange",
},
Object {
"id": "48",
- "name": "pea",
+ "name": "Pea",
},
Object {
"id": "49",
- "name": "peach",
+ "name": "Peach",
},
Object {
"id": "50",
- "name": "pear",
+ "name": "Pear",
},
Object {
"id": "51",
- "name": "pepper",
+ "name": "Pepper",
},
Object {
"id": "52",
- "name": "pineapple",
+ "name": "Pineapple",
},
Object {
"id": "53",
- "name": "pink",
+ "name": "Pink",
},
Object {
"id": "54",
- "name": "pirate",
+ "name": "Pirate",
},
Object {
"id": "55",
- "name": "plum",
+ "name": "Plum",
},
Object {
"id": "56",
- "name": "plushie",
+ "name": "Plushie",
},
Object {
"id": "57",
- "name": "purple",
+ "name": "Purple",
},
Object {
"id": "58",
- "name": "quigukiboy",
+ "name": "Quigukiboy",
},
Object {
"id": "59",
- "name": "quigukigirl",
+ "name": "Quigukigirl",
},
Object {
"id": "60",
- "name": "rainbow",
+ "name": "Rainbow",
},
Object {
"id": "61",
- "name": "red",
+ "name": "Red",
},
Object {
"id": "62",
- "name": "robot",
+ "name": "Robot",
},
Object {
"id": "63",
- "name": "royalboy",
+ "name": "Royalboy",
},
Object {
"id": "64",
- "name": "royalgirl",
+ "name": "Royalgirl",
},
Object {
"id": "65",
- "name": "shadow",
+ "name": "Shadow",
},
Object {
"id": "66",
- "name": "silver",
+ "name": "Silver",
},
Object {
"id": "67",
- "name": "sketch",
+ "name": "Sketch",
},
Object {
"id": "68",
- "name": "skunk",
+ "name": "Skunk",
},
Object {
"id": "69",
- "name": "snot",
+ "name": "Snot",
},
Object {
"id": "70",
- "name": "snow",
+ "name": "Snow",
},
Object {
"id": "71",
- "name": "speckled",
+ "name": "Speckled",
},
Object {
"id": "72",
- "name": "split",
+ "name": "Split",
},
Object {
"id": "73",
- "name": "sponge",
+ "name": "Sponge",
},
Object {
"id": "74",
- "name": "spotted",
+ "name": "Spotted",
},
Object {
"id": "75",
- "name": "starry",
+ "name": "Starry",
},
Object {
"id": "76",
- "name": "strawberry",
+ "name": "Strawberry",
},
Object {
"id": "77",
- "name": "striped",
+ "name": "Striped",
},
Object {
"id": "78",
- "name": "thornberry",
+ "name": "Thornberry",
},
Object {
"id": "79",
- "name": "tomato",
+ "name": "Tomato",
},
Object {
"id": "80",
- "name": "tyrannian",
+ "name": "Tyrannian",
},
Object {
"id": "81",
- "name": "usuki boy",
+ "name": "Usuki boy",
},
Object {
"id": "82",
- "name": "usuki girl",
+ "name": "Usuki girl",
},
Object {
"id": "83",
- "name": "white",
+ "name": "White",
},
Object {
"id": "84",
- "name": "yellow",
+ "name": "Yellow",
},
Object {
"id": "85",
- "name": "zombie",
+ "name": "Zombie",
},
Object {
"id": "86",
- "name": "onion",
+ "name": "Onion",
},
Object {
"id": "87",
- "name": "magma",
+ "name": "Magma",
},
Object {
"id": "88",
- "name": "relic",
+ "name": "Relic",
},
Object {
"id": "89",
- "name": "woodland",
+ "name": "Woodland",
},
Object {
"id": "90",
- "name": "transparent",
+ "name": "Transparent",
},
Object {
"id": "91",
- "name": "maractite",
+ "name": "Maractite",
},
Object {
"id": "92",
@@ -1452,47 +1448,47 @@ describe("Color", () => {
},
Object {
"id": "93",
- "name": "swamp gas",
+ "name": "Swamp gas",
},
Object {
"id": "94",
- "name": "water",
+ "name": "Water",
},
Object {
"id": "95",
- "name": "wraith",
+ "name": "Wraith",
},
Object {
"id": "96",
- "name": "eventide",
+ "name": "Eventide",
},
Object {
"id": "97",
- "name": "elderlyboy",
+ "name": "Elderlyboy",
},
Object {
"id": "98",
- "name": "elderlygirl",
+ "name": "Elderlygirl",
},
Object {
"id": "99",
- "name": "stealthy",
+ "name": "Stealthy",
},
Object {
"id": "100",
- "name": "dimensional",
+ "name": "Dimensional",
},
Object {
"id": "101",
- "name": "agueena",
+ "name": "Agueena",
},
Object {
"id": "102",
- "name": "pastel",
+ "name": "Pastel",
},
Object {
"id": "103",
- "name": "ummagine",
+ "name": "Ummagine",
},
Object {
"id": "104",
@@ -1504,7 +1500,7 @@ describe("Color", () => {
},
Object {
"id": "106",
- "name": "marble",
+ "name": "Marble",
},
Object {
"id": "107",
@@ -1536,13 +1532,12 @@ describe("Color", () => {
expect(queryFn.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
- "SELECT * FROM colors",
+ "SELECT * FROM colors WHERE prank = 0",
],
Array [
"SELECT * FROM color_translations
- WHERE color_id IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) AND locale = \\"en\\"",
+ WHERE color_id IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) AND locale = \\"en\\"",
Array [
- "-1",
"1",
"2",
"3",
diff --git a/src/server/loaders.js b/src/server/loaders.js
index 729d102..b5a071a 100644
--- a/src/server/loaders.js
+++ b/src/server/loaders.js
@@ -1,7 +1,7 @@
const DataLoader = require("dataloader");
const loadAllColors = (db) => async () => {
- const [rows, _] = await db.execute(`SELECT * FROM colors`);
+ const [rows, _] = await db.execute(`SELECT * FROM colors WHERE prank = 0`);
const entities = rows.map(normalizeRow);
return entities;
};
diff --git a/src/server/util.js b/src/server/util.js
new file mode 100644
index 0000000..8751052
--- /dev/null
+++ b/src/server/util.js
@@ -0,0 +1,5 @@
+function capitalize(str) {
+ return str[0].toUpperCase() + str.slice(1);
+}
+
+module.exports = { capitalize };
diff --git a/src/useOutfitState.js b/src/useOutfitState.js
index 317b45f..21aa504 100644
--- a/src/useOutfitState.js
+++ b/src/useOutfitState.js
@@ -94,6 +94,10 @@ const outfitStateReducer = (apolloClient) => (baseState, action) => {
switch (action.type) {
case "rename":
return { ...baseState, name: action.outfitName };
+ case "changeColor":
+ return { ...baseState, colorId: action.colorId };
+ case "changeSpecies":
+ return { ...baseState, speciesId: action.speciesId };
case "wearItem":
return produce(baseState, (state) => {
// A hack to work around https://github.com/immerjs/immer/issues/586
@@ -143,7 +147,7 @@ const outfitStateReducer = (apolloClient) => (baseState, action) => {
closetedItemIds.delete(itemId);
});
default:
- throw new Error(`unexpected action ${action}`);
+ throw new Error(`unexpected action ${JSON.stringify(action)}`);
}
};