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:
parent
3ebbfc4967
commit
c2de6f7167
7 changed files with 65 additions and 8 deletions
|
@ -11,7 +11,8 @@ class AltStylesController < ApplicationController
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { render }
|
format.html { render }
|
||||||
format.json {
|
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],
|
methods: [:series_name, :adjective_name, :thumbnail_url],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ function WardrobePreviewAndControls({
|
||||||
speciesId: outfitState.speciesId,
|
speciesId: outfitState.speciesId,
|
||||||
colorId: outfitState.colorId,
|
colorId: outfitState.colorId,
|
||||||
pose: outfitState.pose,
|
pose: outfitState.pose,
|
||||||
|
altStyleId: outfitState.altStyleId,
|
||||||
appearanceId: outfitState.appearanceId,
|
appearanceId: outfitState.appearanceId,
|
||||||
wornItemIds: outfitState.wornItemIds,
|
wornItemIds: outfitState.wornItemIds,
|
||||||
onChangeHasAnimations: setHasAnimations,
|
onChangeHasAnimations: setHasAnimations,
|
||||||
|
|
|
@ -413,11 +413,12 @@ function ItemSupportPetCompatibilityRuleFields({
|
||||||
*/
|
*/
|
||||||
function ItemSupportAppearanceLayers({ item }) {
|
function ItemSupportAppearanceLayers({ item }) {
|
||||||
const outfitState = React.useContext(OutfitStateContext);
|
const outfitState = React.useContext(OutfitStateContext);
|
||||||
const { speciesId, colorId, pose, appearanceId } = outfitState;
|
const { speciesId, colorId, pose, altStyleId, appearanceId } = outfitState;
|
||||||
const { error, visibleLayers } = useOutfitAppearance({
|
const { error, visibleLayers } = useOutfitAppearance({
|
||||||
speciesId,
|
speciesId,
|
||||||
colorId,
|
colorId,
|
||||||
pose,
|
pose,
|
||||||
|
altStyleId,
|
||||||
appearanceId,
|
appearanceId,
|
||||||
wornItemIds: [item.id],
|
wornItemIds: [item.id],
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,6 +52,7 @@ export function useOutfitPreview({
|
||||||
speciesId,
|
speciesId,
|
||||||
colorId,
|
colorId,
|
||||||
pose,
|
pose,
|
||||||
|
altStyleId,
|
||||||
wornItemIds,
|
wornItemIds,
|
||||||
appearanceId = null,
|
appearanceId = null,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
@ -68,6 +69,7 @@ export function useOutfitPreview({
|
||||||
speciesId,
|
speciesId,
|
||||||
colorId,
|
colorId,
|
||||||
pose,
|
pose,
|
||||||
|
altStyleId,
|
||||||
appearanceId,
|
appearanceId,
|
||||||
wornItemIds,
|
wornItemIds,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
|
|
||||||
import getVisibleLayers, {
|
import getVisibleLayers, {
|
||||||
itemAppearanceFragmentForGetVisibleLayers,
|
itemAppearanceFragmentForGetVisibleLayers,
|
||||||
petAppearanceFragmentForGetVisibleLayers,
|
petAppearanceFragmentForGetVisibleLayers,
|
||||||
} from "../components/getVisibleLayers";
|
} from "./getVisibleLayers";
|
||||||
|
import { useAltStyle } from "../loaders/alt-styles";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* useOutfitAppearance downloads the outfit's appearance data, and returns
|
* useOutfitAppearance downloads the outfit's appearance data, and returns
|
||||||
* visibleLayers for rendering.
|
* visibleLayers for rendering.
|
||||||
*/
|
*/
|
||||||
export default function useOutfitAppearance(outfitState) {
|
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.
|
// 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 items = data2?.items;
|
||||||
const itemAppearances = React.useMemo(
|
const itemAppearances = React.useMemo(
|
||||||
() => (items || []).map((i) => i.appearance),
|
() => (items || []).map((i) => i.appearance),
|
||||||
|
@ -116,8 +125,8 @@ export default function useOutfitAppearance(outfitState) {
|
||||||
const bodyId = petAppearance?.bodyId;
|
const bodyId = petAppearance?.bodyId;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading: loading1 || loading2,
|
loading: loading1 || loading2 || loading3,
|
||||||
error: error1 || error2,
|
error: error1 || error2 || error3,
|
||||||
petAppearance,
|
petAppearance,
|
||||||
items: items || [],
|
items: items || [],
|
||||||
itemAppearances,
|
itemAppearances,
|
||||||
|
|
|
@ -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) {
|
async function loadAltStylesForSpecies(speciesId) {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`/species/${encodeURIComponent(speciesId)}/alt-styles.json`,
|
`/species/${encodeURIComponent(speciesId)}/alt-styles.json`,
|
||||||
|
@ -35,5 +46,37 @@ function normalizeAltStyle(altStyleData) {
|
||||||
seriesName: altStyleData.series_name,
|
seriesName: altStyleData.series_name,
|
||||||
adjectiveName: altStyleData.adjective_name,
|
adjectiveName: altStyleData.adjective_name,
|
||||||
thumbnailUrl: altStyleData.thumbnail_url,
|
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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ class SwfAsset < ApplicationRecord
|
||||||
size_key = size.join('x')
|
size_key = size.join('x')
|
||||||
|
|
||||||
image_dir = "#{self['type']}/#{partition_path}#{self.remote_id}"
|
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
|
end
|
||||||
|
|
||||||
def images
|
def images
|
||||||
|
|
Loading…
Reference in a new issue