From 9a4e114964acee4877f04ea4118b35f144d28a59 Mon Sep 17 00:00:00 2001 From: Matchu Date: Sat, 14 Dec 2013 17:19:27 -0600 Subject: [PATCH] oh yum, this is really starting to come together :) --- app/assets/stylesheets/outfits/_new.sass | 35 +++++++- app/controllers/outfits_controller.rb | 21 ++++- app/helpers/outfits_helper.rb | 21 +++++ app/models/item.rb | 106 ++++++++++++++++++++++- app/views/outfits/new.html.haml | 31 ++++--- 5 files changed, 196 insertions(+), 18 deletions(-) diff --git a/app/assets/stylesheets/outfits/_new.sass b/app/assets/stylesheets/outfits/_new.sass index f668e35d..752e036f 100644 --- a/app/assets/stylesheets/outfits/_new.sass +++ b/app/assets/stylesheets/outfits/_new.sass @@ -138,12 +138,35 @@ body.outfits-new #whats-new margin-bottom: 1em + + h3 + font-size: 125% + font-style: italic + margin-bottom: .5em + + #newest-modeled-items + text-align: center + + .object + margin: 2px + padding: 0 + width: 80px + + .name + display: none + + img + margin: 0 + + .nc-icon + right: 0 - #newest-items + #newest-unmodeled-items list-style: none li +clearfix + margin: .5em 0 a.header background: $module-bg-color @@ -153,7 +176,7 @@ body.outfits-new color: white display: block margin-left: 81px - padding: .5em .75em + padding: .5em 8px position: relative text-decoration: none text-shadow: $text-color 1px 1px 2px @@ -184,6 +207,14 @@ body.outfits-new border-radius: 6px 0 6px 6px height: 80px width: 80px + + .missing-bodies + font-size: 85% + margin-left: 82px + padding: .5em 8px + p + font-family: $main-font + margin-bottom: .5em #latest-contribution +subtle-banner diff --git a/app/controllers/outfits_controller.rb b/app/controllers/outfits_controller.rb index 87de9ad6..9f78e085 100644 --- a/app/controllers/outfits_controller.rb +++ b/app/controllers/outfits_controller.rb @@ -47,8 +47,25 @@ class OutfitsController < ApplicationController end unless localized_fragment_exist?('outfits#new newest_items') - @newest_items = Item.newest.select([:id, :thumbnail_url]). - includes(:translations).limit(9) + newest_items = Item.newest.select([:id, :thumbnail_url, :rarity_index]). + includes(:translations).limit(18) + @newest_modeled_items, @newest_unmodeled_items = + newest_items.partition(&:predicted_fully_modeled?) + + @newest_unmodeled_items_predicted_missing_species_by_color = {} + @newest_unmodeled_items_predicted_modeled_ratio = {} + @newest_unmodeled_items.each do |item| + h = item.predicted_missing_nonstandard_body_species_by_color( + Color.includes(:translations).select([:id]), + Species.includes(:translations).select([:id])) + standard_species = item.predicted_missing_standard_body_species. + select([:id]).includes(:translations) + h[:standard] = standard_species if standard_species.present? + @newest_unmodeled_items_predicted_missing_species_by_color[item] = h + @newest_unmodeled_items_predicted_modeled_ratio[item] = item.predicted_modeled_ratio + end + + @species_count = Species.count end unless localized_fragment_exist?('outfits#new latest_contribution') diff --git a/app/helpers/outfits_helper.rb b/app/helpers/outfits_helper.rb index cae91f76..13a3bb6b 100644 --- a/app/helpers/outfits_helper.rb +++ b/app/helpers/outfits_helper.rb @@ -53,6 +53,27 @@ module OutfitsHelper def remote_load_pet_path "http://#{Rails.configuration.neopia_host}/api/1/pet/customization" end + + def render_predicted_missing_species_by_color(species_by_color) + # TODO: i18n + standard = species_by_color.delete(:standard) + sorted_pairs = species_by_color.to_a.map { |k, v| [k.human_name, v] }. + sort_by { |k, v| k } + sorted_pairs.unshift(['', standard]) if standard + species_by_color[:standard] = standard # undo mutation + + first = true + contents = sorted_pairs.map { |color_human_name, species| + species_sentence = species.map(&:human_name).sort.to_sentence( + two_words_connector: ' or ', last_word_connector: ', or ') + content = first ? "Have you seen the #{color_human_name} #{species_sentence} wearing this item?" : "Or maybe the #{color_human_name} #{species_sentence}?" + first = false + content + } + contents.last << " If so, please model it above! Thanks!" + content_tags = contents.map { |c| content_tag(:p, c) } + content_tags.join('').html_safe + end def outfit_creation_summary(outfit) user = outfit.user diff --git a/app/models/item.rb b/app/models/item.rb index 00fa89e7..dfc6af0a 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -193,11 +193,9 @@ class Item < ActiveRecord::Base end def supported_species_ids - body_ids = swf_assets.select([:body_id]).map(&:body_id) + return Species.select([:id]).map(&:id) if modeled_body_ids.include?(0) - return Species.select([:id]).map(&:id) if body_ids.include?(0) - - pet_types = PetType.where(:body_id => body_ids).select('DISTINCT species_id') + pet_types = PetType.where(:body_id => modeled_body_ids).select('DISTINCT species_id') species_ids = pet_types.map(&:species_id) # If there are multiple known supported species, it probably supports them @@ -209,6 +207,106 @@ class Item < ActiveRecord::Base species_support_ids.blank? || species_support_ids.include?(species.id) end + def modeled_body_ids + @modeled_body_ids ||= swf_assets.select('DISTINCT body_id').map(&:body_id) + end + + def modeled_color_ids + # Might be empty if modeled_body_ids is 0. But it's currently not called + # in that scenario, so, whatever. + @modeled_color_ids ||= PetType.select('DISTINCT color_id'). + where(body_id: modeled_body_ids). + map(&:color_id) + end + + def modeled_colors + @modeled_colors ||= Color.select([:id, :standard]).find(modeled_color_ids) + end + + def modeled_standard_colors? + modeled_colors.any?(&:standard) + end + + def modeled_nonstandard_colors + modeled_colors.reject(&:standard) + end + + def predicted_body_ids + @predicted_body_ids ||= if modeled_body_ids.include?(0) + # Oh, look, it's already known to fit everybody! Sweet. We're done. (This + # isn't folded into the case below, in case this item somehow got a + # body-specific and non-body-specific asset. In all the cases I've seen + # it, that indicates a glitched item, but this method chooses to reflect + # behavior elsewhere in the app by saying that we can put this item on + # anybody. (Heh. Any body.)) + modeled_body_ids + elsif modeled_body_ids.size == 1 + # This might just be a species-specific item. Let's be conservative in + # our prediction, though we'll revise it if we see another body ID. + modeled_body_ids + else + # If an item is worn by more than one body, then it must be wearable by + # all bodies of the same color. (To my knowledge, anyway. I'm not aware + # of any exceptions.) So, let's find those bodies by first finding those + # colors. + PetType.select('DISTINCT body_id'). + where(color_id: modeled_color_ids). + map(&:body_id) + end + end + + def predicted_missing_body_ids + @predicted_missing_body_ids ||= predicted_body_ids - modeled_body_ids + end + + def predicted_missing_standard_body_species_ids + PetType.select('DISTINCT species_id'). + joins(:color). + where(body_id: predicted_missing_body_ids, + colors: {standard: true}). + map(&:species_id) + end + + def predicted_missing_standard_body_species + Species.where(id: predicted_missing_standard_body_species_ids) + end + + def predicted_missing_nonstandard_body_pet_types + PetType.joins(:color). + where(body_id: predicted_missing_body_ids, + colors: {standard: false}) + end + + def predicted_missing_nonstandard_body_species_by_color(colors_scope=Color.scoped, species_scope=Species.scoped) + pet_types = predicted_missing_nonstandard_body_pet_types + + species_by_id = {} + species_scope.find(pet_types.map(&:species_id)).each do |species| + species_by_id[species.id] = species + end + + colors_by_id = {} + colors_scope.find(pet_types.map(&:color_id)).each do |color| + colors_by_id[color.id] = color + end + + species_by_color = {} + pet_types.each do |pt| + color = colors_by_id[pt.color_id] + species_by_color[color] ||= [] + species_by_color[color] << species_by_id[pt.species_id] + end + species_by_color + end + + def predicted_fully_modeled? + predicted_missing_body_ids.empty? + end + + def predicted_modeled_ratio + modeled_body_ids.size.to_f / predicted_body_ids.size + end + def as_json(options={}) json = { :description => description, diff --git a/app/views/outfits/new.html.haml b/app/views/outfits/new.html.haml index 5f8baae6..6ae99fed 100644 --- a/app/views/outfits/new.html.haml +++ b/app/views/outfits/new.html.haml @@ -75,16 +75,27 @@ #whats-new -# TODO: remove newest_items.header i18n key? - localized_cache 'outfits#new newest_items' do - %ul#newest-items - - @newest_items.each do |item| - %li - = link_to image_tag(item.thumbnail_url), item, :class => 'image-link' - = link_to item, :class => 'header' do - - lolremove = rand(54) - -# TODO: i18n - %h2= item.name - -#: we need #{lolremove} more models - %span.meter{style: "width: #{100-lolremove/0.54}%"} + - if @newest_unmodeled_items.present? + %h3 We need your help! Can you model these items? + %ul#newest-unmodeled-items + - @newest_unmodeled_items.each do |item| + %li + = link_to image_tag(item.thumbnail_url), item, :class => 'image-link' + = link_to item, :class => 'header' do + -# TODO: i18n + %h2= item.name + -#: we need #{lolremove} more models + %span.meter{style: "width: #{@newest_unmodeled_items_predicted_modeled_ratio[item]*100}%"} + .missing-bodies + = render_predicted_missing_species_by_color(@newest_unmodeled_items_predicted_missing_species_by_color[item]) + - if @newest_modeled_items.present? + %h3 These items have already been modeled—thanks for your help! + %ul#newest-modeled-items + - @newest_modeled_items.each do |item| + %li.object + = link_to item, title: item.name, alt: item.name do + = image_tag item.thumbnail_url + = nc_icon_for(item) - localized_cache :action_suffix => 'templates' do