Display alt styles in outfit editor when selected

Yay it works(*)! But two major missing pieces:

- Outfit saving doesn't persist it at all
- Item compatibility is unaffected: items will still appear in search
  and in the preview, even when they don't fit anymore.
This commit is contained in:
Emi Matchu 2024-01-30 07:01:03 -08:00
parent 3ebbfc4967
commit c2de6f7167
7 changed files with 65 additions and 8 deletions

View file

@ -11,7 +11,8 @@ class AltStylesController < ApplicationController
respond_to do |format|
format.html { render }
format.json {
render json: @alt_styles.as_json(
render json: @alt_styles.includes(swf_assets: [:zone]).as_json(
include: {swf_assets: {include: [:zone], methods: [:image_url]}},
methods: [:series_name, :adjective_name, :thumbnail_url],
)
}

View file

@ -24,6 +24,7 @@ function WardrobePreviewAndControls({
speciesId: outfitState.speciesId,
colorId: outfitState.colorId,
pose: outfitState.pose,
altStyleId: outfitState.altStyleId,
appearanceId: outfitState.appearanceId,
wornItemIds: outfitState.wornItemIds,
onChangeHasAnimations: setHasAnimations,

View file

@ -413,11 +413,12 @@ function ItemSupportPetCompatibilityRuleFields({
*/
function ItemSupportAppearanceLayers({ item }) {
const outfitState = React.useContext(OutfitStateContext);
const { speciesId, colorId, pose, appearanceId } = outfitState;
const { speciesId, colorId, pose, altStyleId, appearanceId } = outfitState;
const { error, visibleLayers } = useOutfitAppearance({
speciesId,
colorId,
pose,
altStyleId,
appearanceId,
wornItemIds: [item.id],
});

View file

@ -52,6 +52,7 @@ export function useOutfitPreview({
speciesId,
colorId,
pose,
altStyleId,
wornItemIds,
appearanceId = null,
isLoading = false,
@ -68,6 +69,7 @@ export function useOutfitPreview({
speciesId,
colorId,
pose,
altStyleId,
appearanceId,
wornItemIds,
});

View file

@ -1,17 +1,20 @@
import React from "react";
import gql from "graphql-tag";
import { useQuery } from "@apollo/client";
import getVisibleLayers, {
itemAppearanceFragmentForGetVisibleLayers,
petAppearanceFragmentForGetVisibleLayers,
} from "../components/getVisibleLayers";
} from "./getVisibleLayers";
import { useAltStyle } from "../loaders/alt-styles";
/**
* useOutfitAppearance downloads the outfit's appearance data, and returns
* visibleLayers for rendering.
*/
export default function useOutfitAppearance(outfitState) {
const { wornItemIds, speciesId, colorId, pose, appearanceId } = outfitState;
const { wornItemIds, speciesId, colorId, pose, altStyleId, appearanceId } =
outfitState;
// We split this query out from the other one, so that we can HTTP cache it.
//
@ -102,7 +105,13 @@ export default function useOutfitAppearance(outfitState) {
},
);
const petAppearance = data1?.petAppearance;
const {
isLoading: loading3,
error: error3,
data: altStyle,
} = useAltStyle(altStyleId, speciesId);
const petAppearance = altStyle?.appearance ?? data1?.petAppearance;
const items = data2?.items;
const itemAppearances = React.useMemo(
() => (items || []).map((i) => i.appearance),
@ -116,8 +125,8 @@ export default function useOutfitAppearance(outfitState) {
const bodyId = petAppearance?.bodyId;
return {
loading: loading1 || loading2,
error: error1 || error2,
loading: loading1 || loading2 || loading3,
error: error1 || error2 || error3,
petAppearance,
items: items || [],
itemAppearances,

View file

@ -8,6 +8,17 @@ export function useAltStylesForSpecies(speciesId, options = {}) {
});
}
// NOTE: This is actually just a wrapper for `useAltStylesForSpecies`, to share
// the same cache key!
export function useAltStyle(id, speciesId, options = {}) {
const query = useAltStylesForSpecies(speciesId, options);
return {
...query,
data: query.data?.find((s) => s.id === id) ?? null,
};
}
async function loadAltStylesForSpecies(speciesId) {
const res = await fetch(
`/species/${encodeURIComponent(speciesId)}/alt-styles.json`,
@ -35,5 +46,37 @@ function normalizeAltStyle(altStyleData) {
seriesName: altStyleData.series_name,
adjectiveName: altStyleData.adjective_name,
thumbnailUrl: altStyleData.thumbnail_url,
// This matches the PetAppearanceForOutfitPreview GQL fragment!
appearance: {
bodyId: String(altStyleData.body_id),
pose: "UNKNOWN",
isGlitched: false,
species: { id: String(altStyleData.species_id) },
color: { id: String(altStyleData.species_id) },
layers: altStyleData.swf_assets.map(normalizeSwfAssetToLayer),
restrictedZones: [],
},
};
}
function normalizeSwfAssetToLayer(swfAssetData) {
return {
id: String(swfAssetData.id),
zone: {
id: String(swfAssetData.zone.id),
depth: swfAssetData.zone.depth,
label: swfAssetData.zone.label,
},
bodyId: swfAssetData.body_id,
knownGlitches: [], // TODO
// HACK: We're just simplifying this adapter, but it would be better to
// actually check what file formats the manifest says!
// TODO: For example, these do generally have SVGs, we could use them!
svgUrl: null,
canvasMovieLibraryUrl: null,
imageUrl: swfAssetData.image_url,
swfUrl: swfAssetData.url,
};
}

View file

@ -46,7 +46,7 @@ class SwfAsset < ApplicationRecord
size_key = size.join('x')
image_dir = "#{self['type']}/#{partition_path}#{self.remote_id}"
"//#{host}/#{image_dir}/#{size_key}.png?#{image_version}"
"https://#{host}/#{image_dir}/#{size_key}.png?#{image_version}"
end
def images