forked from OpenNeo/impress
Add WIP styles tab to the pose picker
It shows the styles! You can select between them, but it currently does nothing, womp womp!
This commit is contained in:
parent
e2ab8bbc9c
commit
514c99fb42
4 changed files with 180 additions and 3 deletions
|
@ -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
|
||||
|
|
|
@ -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({
|
|||
</Box>
|
||||
</SupportOnly>
|
||||
</TabPanel>
|
||||
<TabPanel>WIP: Styles go here!</TabPanel>
|
||||
<TabPanel>
|
||||
<StyleSelect altStyles={altStyles} />
|
||||
<StyleExplanation />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
<TabList paddingX="2" paddingY="1">
|
||||
<Tab width="50%">Expressions</Tab>
|
||||
|
@ -539,6 +550,126 @@ function PosePickerEmptyExplanation() {
|
|||
);
|
||||
}
|
||||
|
||||
function StyleSelect({ altStyles }) {
|
||||
const [selectedStyleId, setSelectedStyleId] = React.useState(null);
|
||||
|
||||
const defaultStyle = { id: null, adjectiveName: "Default" };
|
||||
|
||||
return (
|
||||
<Flex
|
||||
as="form"
|
||||
direction="column"
|
||||
gap="2"
|
||||
maxHeight="40vh"
|
||||
padding="4px"
|
||||
overflow="auto"
|
||||
>
|
||||
<StyleOption
|
||||
altStyle={defaultStyle}
|
||||
checked={selectedStyleId == null}
|
||||
onChange={setSelectedStyleId}
|
||||
/>
|
||||
{altStyles.map((altStyle) => (
|
||||
<StyleOption
|
||||
key={altStyle.id}
|
||||
altStyle={altStyle}
|
||||
checked={selectedStyleId === altStyle.id}
|
||||
onChange={setSelectedStyleId}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<ClassNames>
|
||||
{({ css, cx }) => (
|
||||
<Box
|
||||
as="label"
|
||||
cursor="pointer"
|
||||
onClick={(e) => {
|
||||
// HACK: We need the timeout to beat the popover's focus stealing!
|
||||
const input = e.currentTarget.querySelector("input");
|
||||
setTimeout(() => input.focus(), 0);
|
||||
}}
|
||||
>
|
||||
<VisuallyHidden
|
||||
as="input"
|
||||
type="radio"
|
||||
name="style"
|
||||
value={altStyle.id}
|
||||
checked={checked}
|
||||
onChange={(e) => onChange(altStyle.id)}
|
||||
/>
|
||||
<Flex
|
||||
alignItems="center"
|
||||
gap="2"
|
||||
borderRadius="md"
|
||||
// HACK: Don't let the thumbnail image overlap the border
|
||||
paddingLeft="2px"
|
||||
paddingY="2px"
|
||||
className={cx(css`
|
||||
border: 2px solid transparent;
|
||||
opacity: 0.8;
|
||||
transition: all 0.2s;
|
||||
|
||||
input:focus + & {
|
||||
box-shadow: ${outlineShadow};
|
||||
}
|
||||
|
||||
input:checked + & {
|
||||
border-color: ${selectedBorderColor};
|
||||
opacity: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
`)}
|
||||
>
|
||||
{altStyle.thumbnailUrl ? (
|
||||
<Box
|
||||
as="img"
|
||||
src={altStyle.thumbnailUrl}
|
||||
alt="Item thumbnail"
|
||||
width="40px"
|
||||
height="40px"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<Box width="40px" height="40px" />
|
||||
)}
|
||||
<Box width="2" />
|
||||
{altStyle.adjectiveName}
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</ClassNames>
|
||||
);
|
||||
}
|
||||
|
||||
function StyleExplanation() {
|
||||
return (
|
||||
<Box
|
||||
fontSize="xs"
|
||||
fontStyle="italic"
|
||||
textAlign="center"
|
||||
opacity="0.7"
|
||||
marginTop="2"
|
||||
>
|
||||
"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.
|
||||
<br />
|
||||
WIP: The styles can't actually be applied yet!
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function EmojiImage({ src, alt, boxSize = 16 }) {
|
||||
return <img src={src} alt={alt} width={boxSize} height={boxSize} />;
|
||||
}
|
||||
|
|
38
app/javascript/wardrobe-2020/loaders/alt-styles.js
Normal file
38
app/javascript/wardrobe-2020/loaders/alt-styles.js
Normal file
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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!
|
||||
|
|
Loading…
Reference in a new issue