diff --git a/app/controllers/alt_styles_controller.rb b/app/controllers/alt_styles_controller.rb
index 46f9f7fd..94bb3cd2 100644
--- a/app/controllers/alt_styles_controller.rb
+++ b/app/controllers/alt_styles_controller.rb
@@ -10,7 +10,11 @@ class AltStylesController < ApplicationController
respond_to do |format|
format.html { render }
- format.json { render json: @alt_styles }
+ format.json {
+ render json: @alt_styles.as_json(
+ methods: [:adjective_name, :thumbnail_url],
+ )
+ }
end
end
end
diff --git a/app/javascript/wardrobe-2020/WardrobePage/PosePicker.js b/app/javascript/wardrobe-2020/WardrobePage/PosePicker.js
index 21bf4339..9b33440d 100644
--- a/app/javascript/wardrobe-2020/WardrobePage/PosePicker.js
+++ b/app/javascript/wardrobe-2020/WardrobePage/PosePicker.js
@@ -20,6 +20,7 @@ import {
useColorModeValue,
useTheme,
useToast,
+ useToken,
} from "@chakra-ui/react";
import { loadable } from "../util";
@@ -27,6 +28,7 @@ import { petAppearanceFragment } from "../components/useOutfitAppearance";
import getVisibleLayers from "../components/getVisibleLayers";
import { OutfitLayers } from "../components/OutfitPreview";
import SupportOnly from "./support/SupportOnly";
+import { useAltStylesForSpecies } from "../loaders/alt-styles";
import useSupport from "./support/useSupport";
import { useLocalStorage } from "../util";
@@ -69,7 +71,8 @@ function PosePicker({
...props
}) {
const initialFocusRef = React.useRef();
- const { loading, error, poseInfos } = usePoses(speciesId, colorId, pose);
+ const posesQuery = usePoses(speciesId, colorId, pose);
+ const altStylesQuery = useAltStylesForSpecies(speciesId);
const [isInSupportMode, setIsInSupportMode] = useLocalStorage(
"DTIPosePickerIsInSupportMode",
false,
@@ -77,6 +80,11 @@ function PosePicker({
const { isSupportUser } = useSupport();
const toast = useToast();
+ const loading = posesQuery.loading || altStylesQuery.loading;
+ const error = posesQuery.error ?? altStylesQuery.error;
+ const poseInfos = posesQuery.poseInfos;
+ const altStyles = altStylesQuery.data ?? [];
+
// Resize the Popover when we toggle support mode, because it probably will
// affect the content size.
React.useLayoutEffect(() => {
@@ -201,7 +209,10 @@ function PosePicker({
- WIP: Styles go here!
+
+
+
+
Expressions
@@ -539,6 +550,126 @@ function PosePickerEmptyExplanation() {
);
}
+function StyleSelect({ altStyles }) {
+ const [selectedStyleId, setSelectedStyleId] = React.useState(null);
+
+ const defaultStyle = { id: null, adjectiveName: "Default" };
+
+ return (
+
+
+ {altStyles.map((altStyle) => (
+
+ ))}
+
+ );
+}
+
+function StyleOption({ altStyle, checked, onChange }) {
+ const theme = useTheme();
+ const selectedBorderColor = useColorModeValue(
+ theme.colors.green["600"],
+ theme.colors.green["300"],
+ );
+ const outlineShadow = useToken("shadows", "outline");
+
+ return (
+
+ {({ css, cx }) => (
+ {
+ // HACK: We need the timeout to beat the popover's focus stealing!
+ const input = e.currentTarget.querySelector("input");
+ setTimeout(() => input.focus(), 0);
+ }}
+ >
+ onChange(altStyle.id)}
+ />
+
+ {altStyle.thumbnailUrl ? (
+
+ ) : (
+
+ )}
+
+ {altStyle.adjectiveName}
+
+
+ )}
+
+ );
+}
+
+function StyleExplanation() {
+ return (
+
+ "Alt Styles" are special NC items that override the pet's usual appearance
+ via the "Styling Studio". The pet's color doesn't have to match.
+
+ WIP: The styles can't actually be applied yet!
+
+ );
+}
+
function EmojiImage({ src, alt, boxSize = 16 }) {
return
;
}
diff --git a/app/javascript/wardrobe-2020/loaders/alt-styles.js b/app/javascript/wardrobe-2020/loaders/alt-styles.js
new file mode 100644
index 00000000..237a103c
--- /dev/null
+++ b/app/javascript/wardrobe-2020/loaders/alt-styles.js
@@ -0,0 +1,38 @@
+import { useQuery } from "@tanstack/react-query";
+
+export function useAltStylesForSpecies(speciesId, options = {}) {
+ return useQuery({
+ ...options,
+ queryKey: ["altStylesForSpecies", String(speciesId)],
+ queryFn: () => loadAltStylesForSpecies(speciesId),
+ });
+}
+
+async function loadAltStylesForSpecies(speciesId) {
+ const res = await fetch(
+ `/species/${encodeURIComponent(speciesId)}/alt-styles.json`,
+ );
+
+ if (!res.ok) {
+ throw new Error(
+ `loading alt styles failed: ${res.status} ${res.statusText}`,
+ );
+ }
+
+ return res.json().then(normalizeAltStyles);
+}
+
+function normalizeAltStyles(altStylesData) {
+ return altStylesData.map(normalizeAltStyle);
+}
+
+function normalizeAltStyle(altStyleData) {
+ return {
+ id: altStyleData.id,
+ speciesId: altStyleData.species_id,
+ colorId: altStyleData.color_id,
+ bodyId: altStyleData.body_id,
+ adjectiveName: altStyleData.adjective_name,
+ thumbnailUrl: altStyleData.thumbnail_url,
+ };
+}
diff --git a/app/models/alt_style.rb b/app/models/alt_style.rb
index 242799e9..135fdfcf 100644
--- a/app/models/alt_style.rb
+++ b/app/models/alt_style.rb
@@ -11,6 +11,10 @@ class AltStyle < ApplicationRecord
species_human_name: species.human_name)
end
+ def adjective_name
+ "Nostalgic #{color.human_name}"
+ end
+
def thumbnail_url
# HACK: Just assume this is a Nostalgic Alt Style, and that the thumbnail
# is named reliably!