forked from OpenNeo/impress
Merge branch 'item-appearances'
This commit is contained in:
commit
f56e1ad426
16 changed files with 179 additions and 178 deletions
8
app/controllers/item_appearances_controller.rb
Normal file
8
app/controllers/item_appearances_controller.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
class ItemAppearancesController < ApplicationController
|
||||||
|
def index
|
||||||
|
@item = Item.find(params[:item_id])
|
||||||
|
render json: @item.as_json(
|
||||||
|
only: [:id], methods: [:appearances, :restricted_zones]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,60 +1,4 @@
|
||||||
class SwfAssetsController < ApplicationController
|
class SwfAssetsController < ApplicationController
|
||||||
def index
|
|
||||||
if params[:item_id]
|
|
||||||
item = Item.find(params[:item_id])
|
|
||||||
@swf_assets = item.swf_assets.includes_depth
|
|
||||||
if params[:body_id]
|
|
||||||
@swf_assets = @swf_assets.fitting_body_id(params[:body_id])
|
|
||||||
else
|
|
||||||
if item.special_color
|
|
||||||
@swf_assets = @swf_assets.fitting_color(item.special_color)
|
|
||||||
else
|
|
||||||
@swf_assets = @swf_assets.fitting_standard_body_ids
|
|
||||||
end
|
|
||||||
json = @swf_assets.all.group_by(&:body_id)
|
|
||||||
end
|
|
||||||
elsif params[:pet_type_id] && params[:item_ids]
|
|
||||||
pet_type = PetType.find(params[:pet_type_id])
|
|
||||||
|
|
||||||
@swf_assets = SwfAsset.object_assets.includes_depth.
|
|
||||||
fitting_body_id(pet_type.body_id).
|
|
||||||
for_item_ids(params[:item_ids]).
|
|
||||||
with_parent_ids
|
|
||||||
json = @swf_assets.map { |a| a.as_json(:parent_id => a.parent_id.to_i, :for => 'wardrobe') }
|
|
||||||
elsif params[:pet_state_id]
|
|
||||||
@swf_assets = PetState.find(params[:pet_state_id]).swf_assets.
|
|
||||||
includes_depth.all
|
|
||||||
pet_state_id = params[:pet_state_id].to_i
|
|
||||||
json = @swf_assets.map { |a| a.as_json(:parent_id => pet_state_id, :for => 'wardrobe') }
|
|
||||||
elsif params[:pet_type_id]
|
|
||||||
@swf_assets = PetType.find(params[:pet_type_id]).pet_states.emotion_order
|
|
||||||
.first.swf_assets.includes_depth
|
|
||||||
elsif params[:ids]
|
|
||||||
@swf_assets = []
|
|
||||||
if params[:ids][:biology]
|
|
||||||
@swf_assets += SwfAsset.includes_depth.biology_assets.where(:remote_id => params[:ids][:biology]).all
|
|
||||||
end
|
|
||||||
if params[:ids][:object]
|
|
||||||
@swf_assets += SwfAsset.includes_depth.object_assets.where(:remote_id => params[:ids][:object]).all
|
|
||||||
end
|
|
||||||
elsif params[:body_id] && params[:item_ids]
|
|
||||||
# DEPRECATED in favor of pet_type_id and item_ids
|
|
||||||
swf_assets = SwfAsset.arel_table
|
|
||||||
@swf_assets = SwfAsset.includes_depth.object_assets.
|
|
||||||
select('swf_assets.*, parents_swf_assets.parent_id').
|
|
||||||
fitting_body_id(params[:body_id]).
|
|
||||||
for_item_ids(params[:item_ids])
|
|
||||||
json = @swf_assets.map { |a| a.as_json(:parent_id => a.parent_id.to_i, :for => 'wardrobe') }
|
|
||||||
end
|
|
||||||
if @swf_assets
|
|
||||||
@swf_assets = @swf_assets.all unless @swf_assets.is_a? Array
|
|
||||||
json = @swf_assets unless json
|
|
||||||
else
|
|
||||||
json = nil
|
|
||||||
end
|
|
||||||
render :json => json
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@swf_asset = SwfAsset.find params[:id]
|
@swf_asset = SwfAsset.find params[:id]
|
||||||
render :json => @swf_asset
|
render :json => @swf_asset
|
||||||
|
|
|
@ -19,7 +19,7 @@ module ContributionHelper
|
||||||
:item_link => link)
|
:item_link => link)
|
||||||
output = translate("contributions.contributed_description.main.#{main_key}_html",
|
output = translate("contributions.contributed_description.main.#{main_key}_html",
|
||||||
:item_description => description)
|
:item_description => description)
|
||||||
output << image_tag(item.thumbnail.secure_url) if show_image
|
output << image_tag(item.thumbnail_url) if show_image
|
||||||
output
|
output
|
||||||
else
|
else
|
||||||
translate('contributions.contributed_description.parents.item.blank')
|
translate('contributions.contributed_description.parents.item.blank')
|
||||||
|
|
|
@ -60,7 +60,7 @@ function SpeciesFacesPicker({
|
||||||
);
|
);
|
||||||
|
|
||||||
const allBodiesAreCompatible = compatibleBodies.some(
|
const allBodiesAreCompatible = compatibleBodies.some(
|
||||||
(body) => body.representsAllBodies,
|
(body) => body.id === "0",
|
||||||
);
|
);
|
||||||
const compatibleBodyIds = compatibleBodies.map((body) => body.id);
|
const compatibleBodyIds = compatibleBodies.map((body) => body.id);
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import {
|
||||||
} from "./components/useOutfitAppearance";
|
} from "./components/useOutfitAppearance";
|
||||||
import { useOutfitPreview } from "./components/OutfitPreview";
|
import { useOutfitPreview } from "./components/OutfitPreview";
|
||||||
import { logAndCapture, useLocalStorage } from "./util";
|
import { logAndCapture, useLocalStorage } from "./util";
|
||||||
|
import { useItemAppearances } from "./loaders/items";
|
||||||
|
|
||||||
function ItemPageOutfitPreview({ itemId }) {
|
function ItemPageOutfitPreview({ itemId }) {
|
||||||
const idealPose = React.useMemo(
|
const idealPose = React.useMemo(
|
||||||
|
@ -99,6 +100,15 @@ function ItemPageOutfitPreview({ itemId }) {
|
||||||
const [initialPreferredSpeciesId] = React.useState(preferredSpeciesId);
|
const [initialPreferredSpeciesId] = React.useState(preferredSpeciesId);
|
||||||
const [initialPreferredColorId] = React.useState(preferredColorId);
|
const [initialPreferredColorId] = React.useState(preferredColorId);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: itemAppearancesData,
|
||||||
|
loading: loadingAppearances,
|
||||||
|
error: errorAppearances,
|
||||||
|
} = useItemAppearances(itemId);
|
||||||
|
const itemName = itemAppearancesData?.name ?? "";
|
||||||
|
const itemAppearances = itemAppearancesData?.appearances ?? [];
|
||||||
|
const restrictedZones = itemAppearancesData?.restrictedZones ?? [];
|
||||||
|
|
||||||
// Start by loading the "canonical" pet and item appearance for the outfit
|
// Start by loading the "canonical" pet and item appearance for the outfit
|
||||||
// preview. We'll use this to initialize both the preview and the picker.
|
// preview. We'll use this to initialize both the preview and the picker.
|
||||||
//
|
//
|
||||||
|
@ -123,25 +133,6 @@ function ItemPageOutfitPreview({ itemId }) {
|
||||||
) {
|
) {
|
||||||
item(id: $itemId) {
|
item(id: $itemId) {
|
||||||
id
|
id
|
||||||
name
|
|
||||||
restrictedZones {
|
|
||||||
id
|
|
||||||
label
|
|
||||||
}
|
|
||||||
compatibleBodiesAndTheirZones {
|
|
||||||
body {
|
|
||||||
id
|
|
||||||
representsAllBodies
|
|
||||||
species {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
zones {
|
|
||||||
id
|
|
||||||
label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
canonicalAppearance(
|
canonicalAppearance(
|
||||||
preferredSpeciesId: $preferredSpeciesId
|
preferredSpeciesId: $preferredSpeciesId
|
||||||
preferredColorId: $preferredColorId
|
preferredColorId: $preferredColorId
|
||||||
|
@ -192,21 +183,19 @@ function ItemPageOutfitPreview({ itemId }) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const compatibleBodies =
|
const compatibleBodies = itemAppearances?.map(({ body }) => body) || [];
|
||||||
data?.item?.compatibleBodiesAndTheirZones?.map(({ body }) => body) || [];
|
|
||||||
const compatibleBodiesAndTheirZones =
|
|
||||||
data?.item?.compatibleBodiesAndTheirZones || [];
|
|
||||||
|
|
||||||
// If there's only one compatible body, and the canonical species's name
|
// If there's only one compatible body, and the canonical species's name
|
||||||
// appears in the item name, then this is probably a species-specific item,
|
// appears in the item name, then this is probably a species-specific item,
|
||||||
// and we should adjust the UI to avoid implying that other species could
|
// and we should adjust the UI to avoid implying that other species could
|
||||||
// model it.
|
// model it.
|
||||||
|
const speciesName =
|
||||||
|
data?.item?.canonicalAppearance?.body?.canonicalAppearance?.species?.name ??
|
||||||
|
"";
|
||||||
const isProbablySpeciesSpecific =
|
const isProbablySpeciesSpecific =
|
||||||
compatibleBodies.length === 1 &&
|
compatibleBodies.length === 1 &&
|
||||||
!compatibleBodies[0].representsAllBodies &&
|
compatibleBodies[0] !== "all" &&
|
||||||
(data?.item?.name || "").includes(
|
itemName.toLowerCase().includes(speciesName.toLowerCase());
|
||||||
data?.item?.canonicalAppearance?.body?.canonicalAppearance?.species?.name,
|
|
||||||
);
|
|
||||||
const couldProbablyModelMoreData = !isProbablySpeciesSpecific;
|
const couldProbablyModelMoreData = !isProbablySpeciesSpecific;
|
||||||
|
|
||||||
// TODO: Does this double-trigger the HTTP request with SpeciesColorPicker?
|
// TODO: Does this double-trigger the HTTP request with SpeciesColorPicker?
|
||||||
|
@ -257,7 +246,7 @@ function ItemPageOutfitPreview({ itemId }) {
|
||||||
const borderColor = useColorModeValue("green.700", "green.400");
|
const borderColor = useColorModeValue("green.700", "green.400");
|
||||||
const errorColor = useColorModeValue("red.600", "red.400");
|
const errorColor = useColorModeValue("red.600", "red.400");
|
||||||
|
|
||||||
const error = errorGQL || errorValids;
|
const error = errorGQL || errorAppearances || errorValids;
|
||||||
if (error) {
|
if (error) {
|
||||||
return <Box color="red.400">{error.message}</Box>;
|
return <Box color="red.400">{error.message}</Box>;
|
||||||
}
|
}
|
||||||
|
@ -350,6 +339,7 @@ function ItemPageOutfitPreview({ itemId }) {
|
||||||
// Wait for us to start _requesting_ the appearance, and _then_
|
// Wait for us to start _requesting_ the appearance, and _then_
|
||||||
// for it to load, and _then_ check compatibility.
|
// for it to load, and _then_ check compatibility.
|
||||||
!loadingGQL &&
|
!loadingGQL &&
|
||||||
|
!loadingAppearances &&
|
||||||
!appearance.loading &&
|
!appearance.loading &&
|
||||||
petState.isValid &&
|
petState.isValid &&
|
||||||
!isCompatible && (
|
!isCompatible && (
|
||||||
|
@ -386,14 +376,14 @@ function ItemPageOutfitPreview({ itemId }) {
|
||||||
compatibleBodies={compatibleBodies}
|
compatibleBodies={compatibleBodies}
|
||||||
couldProbablyModelMoreData={couldProbablyModelMoreData}
|
couldProbablyModelMoreData={couldProbablyModelMoreData}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
isLoading={loadingGQL || loadingValids}
|
isLoading={loadingGQL || loadingAppearances || loadingValids}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Flex gridArea="zones" justifySelf="center" align="center">
|
<Flex gridArea="zones" justifySelf="center" align="center">
|
||||||
{compatibleBodiesAndTheirZones.length > 0 && (
|
{itemAppearances.length > 0 && (
|
||||||
<ItemZonesInfo
|
<ItemZonesInfo
|
||||||
compatibleBodiesAndTheirZones={compatibleBodiesAndTheirZones}
|
itemAppearances={itemAppearances}
|
||||||
restrictedZones={data?.item?.restrictedZones || []}
|
restrictedZones={restrictedZones}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Box width="6" />
|
<Box width="6" />
|
||||||
|
@ -526,13 +516,13 @@ function PlayPauseButton({ isPaused, onClick }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemZonesInfo({ compatibleBodiesAndTheirZones, restrictedZones }) {
|
function ItemZonesInfo({ itemAppearances, restrictedZones }) {
|
||||||
// Reorganize the body-and-zones data, into zone-and-bodies data. Also, we're
|
// Reorganize the body-and-zones data, into zone-and-bodies data. Also, we're
|
||||||
// merging zones with the same label, because that's how user-facing zone UI
|
// merging zones with the same label, because that's how user-facing zone UI
|
||||||
// generally works!
|
// generally works!
|
||||||
const zoneLabelsAndTheirBodiesMap = {};
|
const zoneLabelsAndTheirBodiesMap = {};
|
||||||
for (const { body, zones } of compatibleBodiesAndTheirZones) {
|
for (const { body, swfAssets } of itemAppearances) {
|
||||||
for (const zone of zones) {
|
for (const { zone } of swfAssets) {
|
||||||
if (!zoneLabelsAndTheirBodiesMap[zone.label]) {
|
if (!zoneLabelsAndTheirBodiesMap[zone.label]) {
|
||||||
zoneLabelsAndTheirBodiesMap[zone.label] = {
|
zoneLabelsAndTheirBodiesMap[zone.label] = {
|
||||||
zoneLabel: zone.label,
|
zoneLabel: zone.label,
|
||||||
|
|
61
app/javascript/wardrobe-2020/loaders/items.js
Normal file
61
app/javascript/wardrobe-2020/loaders/items.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
export function useItemAppearances(id, options = {}) {
|
||||||
|
return useQuery({
|
||||||
|
...options,
|
||||||
|
queryKey: ["items", String(id)],
|
||||||
|
queryFn: () => loadItemAppearancesData(id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadItemAppearancesData(id) {
|
||||||
|
const res = await fetch(`/items/${encodeURIComponent(id)}/appearances.json`);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`loading item appearances failed: ${res.status} ${res.statusText}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json().then(normalizeItemAppearancesData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeItemAppearancesData(data) {
|
||||||
|
return {
|
||||||
|
name: data.name,
|
||||||
|
appearances: data.appearances.map((appearance) => ({
|
||||||
|
body: normalizeBody(appearance.body),
|
||||||
|
swfAssets: appearance.swf_assets.map((asset) => ({
|
||||||
|
id: String(asset.id),
|
||||||
|
knownGlitches: asset.known_glitches,
|
||||||
|
zone: normalizeZone(asset.zone),
|
||||||
|
restrictedZones: asset.restricted_zones.map((z) => normalizeZone(z)),
|
||||||
|
urls: {
|
||||||
|
swf: asset.urls.swf,
|
||||||
|
png: asset.urls.png,
|
||||||
|
manifest: asset.urls.manifest,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
restrictedZones: data.restricted_zones.map((z) => normalizeZone(z)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBody(body) {
|
||||||
|
if (String(body.id) === "0") {
|
||||||
|
return { id: "0" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: String(body.id),
|
||||||
|
species: {
|
||||||
|
id: String(body.species.id),
|
||||||
|
name: body.species.name,
|
||||||
|
humanName: body.species.humanName,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeZone(zone) {
|
||||||
|
return { id: String(zone.id), label: zone.label, depth: zone.depth };
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
export function useSavedOutfit(id, options) {
|
export function useSavedOutfit(id, options = {}) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
...options,
|
...options,
|
||||||
queryKey: ["outfits", String(id)],
|
queryKey: ["outfits", String(id)],
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
class Image
|
|
||||||
attr_reader :insecure_url, :secure_url
|
|
||||||
|
|
||||||
def initialize(insecure_url, secure_url)
|
|
||||||
@insecure_url = insecure_url
|
|
||||||
@secure_url = secure_url
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.from_insecure_url(insecure_url)
|
|
||||||
# TODO: We used to use a "Camo" server for this, but we don't anymore.
|
|
||||||
# Replace this with actual logic to actually secure the URLs!
|
|
||||||
Image.new insecure_url, insecure_url
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -413,38 +413,11 @@ class Item < ApplicationRecord
|
||||||
modeled_body_ids.size.to_f / predicted_body_ids.size
|
modeled_body_ids.size.to_f / predicted_body_ids.size
|
||||||
end
|
end
|
||||||
|
|
||||||
def thumbnail
|
|
||||||
if thumbnail_url.present?
|
|
||||||
url = thumbnail_url
|
|
||||||
else
|
|
||||||
url = ActionController::Base.helpers.asset_path(
|
|
||||||
"broken_item_thumbnail.gif")
|
|
||||||
end
|
|
||||||
@thumbnail ||= Image.from_insecure_url(url)
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(options={})
|
def as_json(options={})
|
||||||
json = {
|
super({
|
||||||
:description => description,
|
only: [:id, :name, :description, :thumbnail_url, :rarity_index],
|
||||||
:id => id,
|
methods: [:zones_restrict],
|
||||||
:name => name,
|
}.merge(options))
|
||||||
:thumbnail_url => thumbnail.secure_url,
|
|
||||||
:zones_restrict => zones_restrict,
|
|
||||||
:rarity_index => rarity_index,
|
|
||||||
:nc => nc?
|
|
||||||
}
|
|
||||||
|
|
||||||
# Set owned and wanted keys, unless explicitly told not to. (For example,
|
|
||||||
# item proxies don't want us to bother, since they'll override.)
|
|
||||||
unless options.has_key?(:include_hanger_status)
|
|
||||||
options[:include_hanger_status] = true
|
|
||||||
end
|
|
||||||
if options[:include_hanger_status]
|
|
||||||
json[:owned] = owned?
|
|
||||||
json[:wanted] = wanted?
|
|
||||||
end
|
|
||||||
|
|
||||||
json
|
|
||||||
end
|
end
|
||||||
|
|
||||||
before_create do
|
before_create do
|
||||||
|
@ -511,17 +484,34 @@ class Item < ApplicationRecord
|
||||||
def parent_swf_asset_relationships_to_update=(rels)
|
def parent_swf_asset_relationships_to_update=(rels)
|
||||||
@parent_swf_asset_relationships_to_update = rels
|
@parent_swf_asset_relationships_to_update = rels
|
||||||
end
|
end
|
||||||
|
|
||||||
def needed_translations
|
|
||||||
translatable_locales = Set.new(I18n.locales_with_neopets_language_code)
|
|
||||||
translated_locales = Set.new(translations.map(&:locale))
|
|
||||||
translatable_locales - translated_locales
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_cached?(method_name)
|
Body = Struct.new(:id, :species)
|
||||||
# No methods are cached on a full item. This is for duck typing with item
|
Appearance = Struct.new(:body, :swf_assets)
|
||||||
# proxies.
|
def appearances
|
||||||
false
|
all_swf_assets = swf_assets.to_a
|
||||||
|
|
||||||
|
# If there are no assets yet, there are no appearances.
|
||||||
|
return [] if all_swf_assets.empty?
|
||||||
|
|
||||||
|
# Get all SWF assets, and separate the ones that fit everyone (body_id=0).
|
||||||
|
swf_assets_by_body_id = all_swf_assets.group_by(&:body_id)
|
||||||
|
swf_assets_for_all_bodies = swf_assets_by_body_id.delete(0) || []
|
||||||
|
|
||||||
|
# If there are no body-specific assets, return one appearance for them all.
|
||||||
|
if swf_assets_by_body_id.empty?
|
||||||
|
body = Body.new(0, nil)
|
||||||
|
return [Appearance.new(body, swf_assets_for_all_bodies)]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Otherwise, create an appearance for each real (nonzero) body ID. We don't
|
||||||
|
# generally expect body_id = 0 and body_id != 0 to mix, but if they do,
|
||||||
|
# uhh, let's merge the body_id = 0 ones in?
|
||||||
|
swf_assets_by_body_id.map do |body_id, body_specific_assets|
|
||||||
|
swf_assets_for_body = body_specific_assets + swf_assets_for_all_bodies
|
||||||
|
species = Species.with_body_id(body_id).first!
|
||||||
|
body = Body.new(body_id, species)
|
||||||
|
Appearance.new(body, swf_assets_for_body)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.all_by_ids_or_children(ids, swf_assets)
|
def self.all_by_ids_or_children(ids, swf_assets)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
class Species < ApplicationRecord
|
class Species < ApplicationRecord
|
||||||
translates :name
|
translates :name
|
||||||
|
has_many :pet_types
|
||||||
|
|
||||||
scope :alphabetical, -> {
|
scope :alphabetical, -> {
|
||||||
st = Species::Translation.arel_table
|
st = Species::Translation.arel_table
|
||||||
|
@ -12,6 +13,11 @@ class Species < ApplicationRecord
|
||||||
where(st[:name].matches(sanitize_sql_like(name)))
|
where(st[:name].matches(sanitize_sql_like(name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scope :with_body_id, -> body_id {
|
||||||
|
pt = PetType.arel_table
|
||||||
|
joins(:pet_types).where(pt[:body_id].eq(body_id)).limit(1)
|
||||||
|
}
|
||||||
|
|
||||||
# TODO: Should we consider replacing this at call sites? This used to be
|
# TODO: Should we consider replacing this at call sites? This used to be
|
||||||
# built into the globalize gem but isn't anymore!
|
# built into the globalize gem but isn't anymore!
|
||||||
def self.find_by_name(name)
|
def self.find_by_name(name)
|
||||||
|
@ -19,7 +25,7 @@ class Species < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json(options={})
|
def as_json(options={})
|
||||||
{:id => id, :name => human_name}
|
super({only: [:id, :name], methods: [:human_name]}.merge(options))
|
||||||
end
|
end
|
||||||
|
|
||||||
def human_name
|
def human_name
|
||||||
|
|
|
@ -96,22 +96,41 @@ class SwfAsset < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json(options={})
|
def as_json(options={})
|
||||||
json = {
|
super({
|
||||||
:id => remote_id,
|
only: [:id, :known_glitches],
|
||||||
:type => type,
|
methods: [:zone, :restricted_zones, :urls]
|
||||||
:depth => depth,
|
}.merge(options))
|
||||||
:body_id => body_id,
|
end
|
||||||
:zone_id => zone_id,
|
|
||||||
:zones_restrict => zones_restrict,
|
def urls
|
||||||
:is_body_specific => body_specific?,
|
{
|
||||||
# Now that we don't proactively convert images anymore, let's just always
|
swf: url,
|
||||||
# say `has_image: true` when sending data to the frontend, so it'll use the
|
png: image_url,
|
||||||
# new URLs anyway!
|
manifest: manifest_url,
|
||||||
:has_image => true,
|
|
||||||
:images => images
|
|
||||||
}
|
}
|
||||||
json[:parent_id] = options[:parent_id] if options[:parent_id]
|
end
|
||||||
json
|
|
||||||
|
def known_glitches
|
||||||
|
self[:known_glitches].split(',')
|
||||||
|
end
|
||||||
|
|
||||||
|
def known_glitches=(new_known_glitches)
|
||||||
|
if new_known_glitches.is_a? Array
|
||||||
|
new_known_glitches = new_known_glitches.join(',')
|
||||||
|
end
|
||||||
|
self[:known_glitches] = new_known_glitches
|
||||||
|
end
|
||||||
|
|
||||||
|
def restricted_zone_ids
|
||||||
|
[].tap do |ids|
|
||||||
|
zones_restrict.chars.each_with_index do |bit, index|
|
||||||
|
ids << index + 1 if bit == "1"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def restricted_zones
|
||||||
|
Zone.where(id: restricted_zone_ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
def body_specific?
|
def body_specific?
|
||||||
|
|
|
@ -18,6 +18,10 @@ class Zone < ActiveRecord::Base
|
||||||
}
|
}
|
||||||
scope :for_items, -> { where(arel_table[:type_id].gt(1)) }
|
scope :for_items, -> { where(arel_table[:type_id].gt(1)) }
|
||||||
|
|
||||||
|
def as_json(options={})
|
||||||
|
super({only: [:id, :depth, :label]}.merge(options))
|
||||||
|
end
|
||||||
|
|
||||||
def uncertain_label
|
def uncertain_label
|
||||||
@sometimes ? "#{label} sometimes" : label
|
@sometimes ? "#{label} sometimes" : label
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
= link_to item_path(item) do
|
= link_to item_path(item) do
|
||||||
= image_tag item.thumbnail.secure_url, :alt => item.description, :title => item.description
|
= image_tag item.thumbnail_url, :alt => item.description, :title => item.description
|
||||||
%span.name= item.name
|
%span.name= item.name
|
||||||
= nc_icon_for(item)
|
= nc_icon_for(item)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
- canonical_path @item
|
- canonical_path @item
|
||||||
|
|
||||||
%header#item-header
|
%header#item-header
|
||||||
= image_tag @item.thumbnail.secure_url, :id => 'item-thumbnail'
|
= image_tag @item.thumbnail_url, :id => 'item-thumbnail'
|
||||||
%div
|
%div
|
||||||
%h2#item-name= @item.name
|
%h2#item-name= @item.name
|
||||||
= nc_icon_for(@item)
|
= nc_icon_for(@item)
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
- @newest_unmodeled_items.each do |item|
|
- @newest_unmodeled_items.each do |item|
|
||||||
- localized_cache "items/#{item.id} modeling_progress updated_at=#{item.updated_at.to_i}" do
|
- localized_cache "items/#{item.id} modeling_progress updated_at=#{item.updated_at.to_i}" do
|
||||||
%li{'data-item-id' => item.id}
|
%li{'data-item-id' => item.id}
|
||||||
= link_to image_tag(item.thumbnail.secure_url), item, :class => 'image-link'
|
= link_to image_tag(item.thumbnail_url), item, :class => 'image-link'
|
||||||
= link_to item, :class => 'header' do
|
= link_to item, :class => 'header' do
|
||||||
%h2= item.name
|
%h2= item.name
|
||||||
%span.meter{style: "width: #{@newest_unmodeled_items_predicted_modeled_ratio[item]*100}%"}
|
%span.meter{style: "width: #{@newest_unmodeled_items_predicted_modeled_ratio[item]*100}%"}
|
||||||
|
@ -101,7 +101,7 @@
|
||||||
- @newest_modeled_items.each do |item|
|
- @newest_modeled_items.each do |item|
|
||||||
%li.object
|
%li.object
|
||||||
= link_to item, title: item.name, alt: item.name do
|
= link_to item, title: item.name, alt: item.name do
|
||||||
= image_tag item.thumbnail.secure_url
|
= image_tag item.thumbnail_url
|
||||||
= nc_icon_for(item)
|
= nc_icon_for(item)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,25 +7,18 @@ OpenneoImpressItems::Application.routes.draw do
|
||||||
get '/wardrobe' => redirect('/outfits/new')
|
get '/wardrobe' => redirect('/outfits/new')
|
||||||
get '/start/:color_name/:species_name' => 'outfits#start'
|
get '/start/:color_name/:species_name' => 'outfits#start'
|
||||||
|
|
||||||
# DEPRECATED
|
|
||||||
get '/bodies/:body_id/swf_assets.json' => 'swf_assets#index', :as => :body_swf_assets
|
|
||||||
|
|
||||||
get '/items/:item_id/swf_assets.json' => 'swf_assets#index', :as => :item_swf_assets
|
|
||||||
get '/items/:item_id/bodies/:body_id/swf_assets.json' => 'swf_assets#index', :as => :item_swf_assets_for_body_id
|
|
||||||
get '/pet_types/:pet_type_id/swf_assets.json' => 'swf_assets#index', :as => :pet_type_swf_assets
|
|
||||||
get '/pet_types/:pet_type_id/items/swf_assets.json' => 'swf_assets#index', :as => :item_swf_assets_for_pet_type
|
|
||||||
get '/pet_states/:pet_state_id/swf_assets.json' => 'swf_assets#index', :as => :pet_state_swf_assets
|
|
||||||
get '/species/:species_id/color/:color_id/pet_type.json' => 'pet_types#show'
|
get '/species/:species_id/color/:color_id/pet_type.json' => 'pet_types#show'
|
||||||
|
|
||||||
resources :contributions, :only => [:index]
|
resources :contributions, :only => [:index]
|
||||||
resources :items, :only => [:index, :show] do
|
resources :items, :only => [:index, :show] do
|
||||||
|
resources :appearances, controller: 'item_appearances', only: [:index]
|
||||||
collection do
|
collection do
|
||||||
get :needed
|
get :needed
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :outfits, :only => [:show, :create, :update, :destroy]
|
resources :outfits, :only => [:show, :create, :update, :destroy]
|
||||||
resources :pet_attributes, :only => [:index]
|
resources :pet_attributes, :only => [:index]
|
||||||
resources :swf_assets, :only => [:index, :show] do
|
resources :swf_assets, :only => [:show] do
|
||||||
collection do
|
collection do
|
||||||
get :links
|
get :links
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue