Merge branch 'outfit_thumbnails'
7
Gemfile
|
@ -41,6 +41,13 @@ gem 'newrelic_rpm'
|
|||
|
||||
gem 'neopets', :git => 'git://github.com/matchu/neopets.git'
|
||||
|
||||
gem "mini_magick", "~> 3.4"
|
||||
|
||||
gem "fog", "~> 1.1.2"
|
||||
gem "carrierwave", "~> 0.5.8"
|
||||
|
||||
gem "parallel", "~> 0.5.17"
|
||||
|
||||
group :development_async do
|
||||
# async wrappers
|
||||
gem 'eventmachine', :git => 'git://github.com/eventmachine/eventmachine.git'
|
||||
|
|
26
Gemfile.lock
|
@ -86,6 +86,8 @@ GEM
|
|||
arel (2.0.10)
|
||||
bcrypt-ruby (2.1.4)
|
||||
builder (2.1.2)
|
||||
carrierwave (0.5.8)
|
||||
activesupport (~> 3.0)
|
||||
character-encodings (0.4.1)
|
||||
chronic (0.6.7)
|
||||
closure-compiler (1.1.4)
|
||||
|
@ -100,11 +102,23 @@ GEM
|
|||
eventmachine
|
||||
erubis (2.6.6)
|
||||
abstract (>= 1.0.0)
|
||||
excon (0.9.6)
|
||||
factory_girl (2.3.2)
|
||||
activesupport
|
||||
factory_girl_rails (1.4.0)
|
||||
factory_girl (~> 2.3.0)
|
||||
railties (>= 3.0.0)
|
||||
fog (1.1.2)
|
||||
builder
|
||||
excon (~> 0.9.0)
|
||||
formatador (~> 0.2.0)
|
||||
mime-types
|
||||
multi_json (~> 1.0.3)
|
||||
net-scp (~> 1.0.4)
|
||||
net-ssh (>= 2.1.3)
|
||||
nokogiri (~> 1.5.0)
|
||||
ruby-hmac
|
||||
formatador (0.2.1)
|
||||
haml (3.0.25)
|
||||
hoptoad_notifier (2.4.11)
|
||||
activesupport
|
||||
|
@ -122,13 +136,20 @@ GEM
|
|||
treetop (~> 1.4.8)
|
||||
memcache-client (1.8.5)
|
||||
mime-types (1.17.2)
|
||||
mini_magick (3.4)
|
||||
subexec (~> 0.2.1)
|
||||
msgpack (0.4.6)
|
||||
multi_json (1.0.4)
|
||||
mysql2 (0.2.6)
|
||||
net-scp (1.0.4)
|
||||
net-ssh (>= 1.99.1)
|
||||
net-ssh (2.3.0)
|
||||
newrelic_rpm (3.3.3)
|
||||
nokogiri (1.5.3)
|
||||
open4 (1.3.0)
|
||||
openneo-auth-signatory (0.1.0)
|
||||
ruby-hmac
|
||||
parallel (0.5.17)
|
||||
polyglot (0.3.3)
|
||||
rack (1.2.5)
|
||||
rack-fiber_pool (0.9.2)
|
||||
|
@ -189,6 +210,7 @@ GEM
|
|||
sinatra (1.2.8)
|
||||
rack (~> 1.1)
|
||||
tilt (>= 1.2.2, < 2.0)
|
||||
subexec (0.2.1)
|
||||
swf_converter (0.0.3)
|
||||
thor (0.14.6)
|
||||
tilt (1.3.3)
|
||||
|
@ -213,6 +235,7 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
RocketAMF!
|
||||
addressable
|
||||
carrierwave (~> 0.5.8)
|
||||
character-encodings (~> 0.4.1)
|
||||
compass (~> 0.10.1)
|
||||
devise (~> 1.1.5)
|
||||
|
@ -221,10 +244,12 @@ DEPENDENCIES
|
|||
em-synchrony!
|
||||
eventmachine!
|
||||
factory_girl_rails (~> 1.0)
|
||||
fog (~> 1.1.2)
|
||||
haml (~> 3.0.18)
|
||||
hoptoad_notifier
|
||||
jammit (~> 0.5.3)
|
||||
memcache-client (~> 1.8.5)
|
||||
mini_magick (~> 3.4)
|
||||
msgpack (~> 0.4.3)
|
||||
mysql2 (< 0.3)
|
||||
mysqlplus!
|
||||
|
@ -232,6 +257,7 @@ DEPENDENCIES
|
|||
newrelic_rpm
|
||||
nokogiri (~> 1.5.2)
|
||||
openneo-auth-signatory (~> 0.1.0)
|
||||
parallel (~> 0.5.17)
|
||||
rack-fiber_pool
|
||||
rails (= 3.0.5)
|
||||
rdiscount (~> 1.6.5)
|
||||
|
|
|
@ -2,18 +2,11 @@ class OutfitsController < ApplicationController
|
|||
before_filter :find_authorized_outfit, :only => [:update, :destroy]
|
||||
|
||||
def create
|
||||
Rails.logger.debug "Signed in?: #{user_signed_in?}"
|
||||
Rails.logger.debug "User 1: #{current_user.inspect}"
|
||||
@outfit = Outfit.build_for_user(current_user, params[:outfit])
|
||||
Rails.logger.debug "User 2: #{current_user.inspect}"
|
||||
if @outfit.save
|
||||
Rails.logger.debug "User 3: #{current_user.inspect}"
|
||||
render :json => @outfit.id
|
||||
Rails.logger.debug "User 4: #{current_user.inspect}"
|
||||
render :json => @outfit
|
||||
else
|
||||
Rails.logger.debug "User 5: #{current_user.inspect}"
|
||||
render_outfit_errors
|
||||
Rails.logger.debug "User 6: #{current_user.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -82,7 +75,7 @@ class OutfitsController < ApplicationController
|
|||
|
||||
def update
|
||||
if @outfit.update_attributes(params[:outfit])
|
||||
render :json => true
|
||||
render :json => @outfit
|
||||
else
|
||||
render_outfit_errors
|
||||
end
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
module ApplicationHelper
|
||||
def absolute_url(path_or_url)
|
||||
if path_or_url.include?('://') # already an absolute URL
|
||||
path_or_url
|
||||
else # a relative path
|
||||
request.protocol + request.host_with_port + path_or_url
|
||||
end
|
||||
end
|
||||
|
||||
def add_body_class(class_name)
|
||||
@body_class ||= ''
|
||||
@body_class << " #{class_name}"
|
||||
|
@ -101,6 +109,23 @@ module ApplicationHelper
|
|||
hidden_field_tag 'origin', value, :id => nil
|
||||
end
|
||||
|
||||
def open_graph(properties)
|
||||
if @open_graph
|
||||
@open_graph.merge! properties
|
||||
else
|
||||
@open_graph = properties
|
||||
end
|
||||
end
|
||||
|
||||
def open_graph_tags
|
||||
if @open_graph
|
||||
@open_graph.inject('') do |output, property|
|
||||
key, value = property
|
||||
output + tag(:meta, :property => "og:#{key}", :content => value)
|
||||
end.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def return_to_field_tag
|
||||
hidden_field_tag :return_to, request.fullpath
|
||||
end
|
||||
|
|
|
@ -13,9 +13,22 @@ class Outfit < ActiveRecord::Base
|
|||
|
||||
scope :wardrobe_order, order('starred DESC', :name)
|
||||
|
||||
mount_uploader :image, OutfitImageUploader
|
||||
|
||||
before_save :update_enqueued_image
|
||||
after_commit :enqueue_image!
|
||||
|
||||
def as_json(more_options={})
|
||||
serializable_hash :only => [:id, :name, :pet_state_id, :starred],
|
||||
:methods => [:color_id, :species_id, :worn_and_unworn_item_ids]
|
||||
:methods => [:color_id, :species_id, :worn_and_unworn_item_ids,
|
||||
:image_versions, :image_enqueued, :image_layers_hash]
|
||||
end
|
||||
|
||||
def image_versions
|
||||
{}.tap do |versions|
|
||||
versions[:large] = image.url
|
||||
image.versions.each { |name, version| versions[name] = version.url }
|
||||
end
|
||||
end
|
||||
|
||||
def closet_item_ids
|
||||
|
@ -65,8 +78,44 @@ class Outfit < ActiveRecord::Base
|
|||
self.item_outfit_relationships = new_rels
|
||||
end
|
||||
|
||||
def worn_item_ids
|
||||
worn_and_unworn_item_ids[:worn]
|
||||
# Returns the array of SwfAssets representing each layer of the output image,
|
||||
# ordered from bottom to top. Careful: this method is memoized, so if the
|
||||
# image layers change after its first call we'll get bad results.
|
||||
def image_layers
|
||||
@image_layers ||= visible_assets_with_images.sort { |a, b| a.zone.depth <=> b.zone.depth }
|
||||
end
|
||||
|
||||
# Creates and writes the thumbnail images for this outfit iff the new image
|
||||
# would be different than the current one. (Writes to file in development,
|
||||
# S3 in production.) If the image is updated, updates the image layers hash
|
||||
# and runs #save! on the record, so any other changes will also be saved.
|
||||
def write_image!
|
||||
if image_layers_dirty?
|
||||
Tempfile.open(['outfit_image', '.png']) do |image|
|
||||
create_image! image
|
||||
self.image_layers_hash = generate_image_layers_hash
|
||||
self.image = image
|
||||
self.image_enqueued = false
|
||||
save!
|
||||
end
|
||||
end
|
||||
|
||||
self.image
|
||||
end
|
||||
|
||||
# Enqueue an image write iff the new image would be different than the
|
||||
# current one.
|
||||
def enqueue_image!
|
||||
Resque.enqueue(OutfitImageUpdate, id)
|
||||
end
|
||||
|
||||
def update_enqueued_image
|
||||
self.image_enqueued = (image_layers_dirty?)
|
||||
true
|
||||
end
|
||||
|
||||
def s3_key(size)
|
||||
URI.encode("#{id}/#{size.join 'x'}.png")
|
||||
end
|
||||
|
||||
def self.build_for_user(user, params)
|
||||
|
@ -82,5 +131,87 @@ class Outfit < ActiveRecord::Base
|
|||
outfit.attributes = params
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Creates a 600x600 PNG image of this outfit, writing to the given output
|
||||
# file.
|
||||
def create_image!(output)
|
||||
unless image_layers.empty?
|
||||
temp_image_files = Parallel.map(image_layers, :in_threads => 8) do |swf_asset|
|
||||
image_file = Tempfile.open(['outfit_layer', '.png'])
|
||||
write_temp_swf_asset_image!(swf_asset, image_file)
|
||||
image_file.close
|
||||
image_file
|
||||
end
|
||||
|
||||
# Here we do some awkwardness to get the exact ImageMagick command we
|
||||
# want, though it's still less awkward than handling the command
|
||||
# ourselves. Give all of the temporary images as input, flatten them and
|
||||
# write them to the output path.
|
||||
command = MiniMagick::CommandBuilder.new('convert')
|
||||
temp_image_files.each { |image_file| command.push image_file.path }
|
||||
command.layers 'flatten'
|
||||
command.push output.path
|
||||
|
||||
# Though the above command really is sufficient, we still need a dummy
|
||||
# image to handle execution.
|
||||
output_image = MiniMagick::Image.new(output.path)
|
||||
output_image.run(command)
|
||||
|
||||
temp_image_files.each(&:unlink)
|
||||
else
|
||||
output.close
|
||||
end
|
||||
end
|
||||
|
||||
def visible_assets
|
||||
biology_assets = pet_state.swf_assets
|
||||
object_assets = SwfAsset.object_assets.
|
||||
fitting_body_id(pet_state.pet_type.body_id).for_item_ids(worn_item_ids)
|
||||
|
||||
# Now for fun with bitmasks! Rather than building a bunch of integer arrays
|
||||
# here, we instead go low-level and use bit-level operations. Build the
|
||||
# bitmask by parsing the binary string (reversing it to get the lower zone
|
||||
# numbers on the right), then OR them all together to get the mask
|
||||
# representing all the restricted zones. (Note to self: why not just store
|
||||
# in this format in the first place?)
|
||||
restrictors = biology_assets + worn_items
|
||||
restricted_zones_mask = restrictors.inject(0) do |mask, restrictor|
|
||||
mask | restrictor.zones_restrict.reverse.to_i(2)
|
||||
end
|
||||
|
||||
# Now, check each asset's zone is not restricted in the bitmask using
|
||||
# bitwise operations: shift 1 to the zone_id position, then AND it with
|
||||
# the restricted zones mask. If we get 0, then the bit for that zone ID was
|
||||
# not turned on, so the zone is not restricted and this asset is visible.
|
||||
all_assets = biology_assets + object_assets
|
||||
all_assets.select { |a| (1 << (a.zone_id - 1)) & restricted_zones_mask == 0 }
|
||||
end
|
||||
|
||||
def visible_assets_with_images
|
||||
visible_assets.select(&:has_image?)
|
||||
end
|
||||
|
||||
# Generate 8-char hex digest representing visible image layers for this outfit.
|
||||
# Hash function should be decently collision-resistant.
|
||||
def generate_image_layers_hash
|
||||
@generated_image_layers_hash ||=
|
||||
Digest::MD5.hexdigest(image_layers.map(&:id).join(',')).first(8)
|
||||
end
|
||||
|
||||
def image_layers_dirty?
|
||||
generate_image_layers_hash != self.image_layers_hash
|
||||
end
|
||||
|
||||
IMAGE_BASE_SIZE = [600, 600]
|
||||
def write_temp_swf_asset_image!(swf_asset, file)
|
||||
key = swf_asset.s3_key(IMAGE_BASE_SIZE)
|
||||
bucket = SwfAsset::IMAGE_BUCKET
|
||||
data = bucket.get(key)
|
||||
file.binmode # write in binary mode
|
||||
file.truncate(0) # clear the file
|
||||
file.write data # write the new data
|
||||
end
|
||||
end
|
||||
|
||||
|
|
15
app/models/outfit_image_update.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
class OutfitImageUpdate
|
||||
@queue = :outfit_image_updates
|
||||
|
||||
def self.perform(id)
|
||||
Outfit.find(id).write_image!
|
||||
end
|
||||
|
||||
# Represents an outfit image update for an outfit that existed before this
|
||||
# feature was built. Its queue has a lower priority, so new outfits will
|
||||
# be updated before retroactively converted outfits.
|
||||
class Retroactive < OutfitImageUpdate
|
||||
@queue = :retroactive_outfit_image_updates
|
||||
end
|
||||
end
|
||||
|
39
app/models/outfit_image_uploader.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
require 'carrierwave/processing/mime_types'
|
||||
|
||||
class OutfitImageUploader < CarrierWave::Uploader::Base
|
||||
include CarrierWave::MimeTypes
|
||||
include CarrierWave::MiniMagick
|
||||
|
||||
# Settings for S3 storage. Will only be used on production.
|
||||
fog_directory 'impress-outfit-images'
|
||||
fog_attributes 'Cache-Control' => "max-age=#{15.minutes}",
|
||||
'Content-Type' => 'image/png'
|
||||
|
||||
process :set_content_type
|
||||
|
||||
version :medium do
|
||||
process :resize_to_fill => [300, 300]
|
||||
end
|
||||
|
||||
version :small, :from_version => :medium do
|
||||
process :resize_to_fill => [150, 150]
|
||||
end
|
||||
|
||||
def filename
|
||||
"preview.png"
|
||||
end
|
||||
|
||||
def store_dir
|
||||
"outfits/#{partition_dir}"
|
||||
end
|
||||
|
||||
# 123006789 => "123/006/789"
|
||||
def partition_dir
|
||||
partitions.map { |partition| "%03d" % partition }.join('/')
|
||||
end
|
||||
|
||||
# 123006789 => [123, 6, 789]
|
||||
def partitions
|
||||
[6, 3, 0].map { |n| model.id / 10**n % 1000 }
|
||||
end
|
||||
end
|
|
@ -15,8 +15,14 @@ class SwfAsset < ActiveRecord::Base
|
|||
|
||||
set_inheritance_column 'inheritance_type'
|
||||
|
||||
IMAGE_SIZES = {
|
||||
:small => [150, 150],
|
||||
:medium => [300, 300],
|
||||
:large => [600, 600]
|
||||
}
|
||||
|
||||
include SwfConverter
|
||||
converts_swfs :size => [600, 600], :output_sizes => [[150, 150], [300, 300], [600, 600]]
|
||||
converts_swfs :size => IMAGE_SIZES[:large], :output_sizes => IMAGE_SIZES.values
|
||||
|
||||
def local_swf_path
|
||||
LOCAL_ASSET_DIR.join(local_path_within_outfit_swfs)
|
||||
|
@ -75,6 +81,21 @@ class SwfAsset < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def image_version
|
||||
converted_at.to_i
|
||||
end
|
||||
|
||||
def image_url(size=IMAGE_SIZES[:large])
|
||||
host = ASSET_HOSTS[:swf_asset_images]
|
||||
size_key = size.join('x')
|
||||
|
||||
"http://#{host}/#{s3_path}/#{size_key}.png?#{image_version}"
|
||||
end
|
||||
|
||||
def images
|
||||
IMAGE_SIZES.values.map { |size| {:size => size, :url => image_url(size)} }
|
||||
end
|
||||
|
||||
def convert_swf_if_not_converted!
|
||||
if needs_conversion?
|
||||
convert_swf!
|
||||
|
@ -161,7 +182,7 @@ class SwfAsset < ActiveRecord::Base
|
|||
:zones_restrict => zones_restrict,
|
||||
:is_body_specific => body_specific?,
|
||||
:has_image => has_image?,
|
||||
:s3_path => s3_path
|
||||
:images => images
|
||||
}
|
||||
if options[:for] == 'wardrobe'
|
||||
json[:local_path] = local_url
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
|
||||
@import "partials/context_button"
|
||||
@import "partials/icon"
|
||||
@import "partials/outfit"
|
||||
@import star
|
||||
|
||||
$object-padding: 6px
|
||||
$nc-icon-size: 16px
|
||||
|
||||
$preview-dimension: 400px
|
||||
$preview-dimension: 380px
|
||||
$sidebar-margin: 20px
|
||||
$sidebar-width: 400px
|
||||
$sidebar-unit-horizontal-padding: 24px
|
||||
|
@ -21,88 +22,23 @@ $outfit-header-padding: 24px
|
|||
$outfit-content-width: $sidebar-unit-inner-width - $outfit-thumbnail-size - $outfit-thumbnail-margin - 32px
|
||||
$outfit-content-inner-width: $outfit-content-width - $outfit-header-padding
|
||||
|
||||
=user-select($select)
|
||||
select: unquote($select)
|
||||
+experimental(user-select, $select, -moz, -webkit, not -o, not -ms, -khtml, official)
|
||||
|
||||
=active-mode
|
||||
color: $text-color
|
||||
font-weight: bold
|
||||
|
||||
=outfit
|
||||
+outfit-star-shifted
|
||||
padding: .25em 0
|
||||
//.outfit-thumbnail
|
||||
float: left
|
||||
height: $outfit-thumbnail-size
|
||||
margin-right: $outfit-thumbnail-margin
|
||||
overflow: hidden
|
||||
position: relative
|
||||
width: $outfit-thumbnail-size
|
||||
img
|
||||
height: $outfit-thumbnail-original-size
|
||||
left: -$outfit-thumbnail-original-size / 4
|
||||
position: absolute
|
||||
top: -$outfit-thumbnail-original-size / 4
|
||||
width: $outfit-thumbnail-original-size
|
||||
.outfit-delete
|
||||
+reset-awesome-button
|
||||
+opacity(.5)
|
||||
font-size: 150%
|
||||
float: right
|
||||
line-height: 1
|
||||
margin-top: -.125em
|
||||
padding: .125em .25em
|
||||
&:hover
|
||||
+opacity(1)
|
||||
background: $module-bg-color
|
||||
header
|
||||
display: block
|
||||
padding-left: $outfit-header-padding
|
||||
h4
|
||||
cursor: pointer
|
||||
display: inline
|
||||
&:hover
|
||||
text-decoration: underline
|
||||
h4, .outfit-rename-field
|
||||
font-size: 115%
|
||||
.outfit-rename-button, .outfit-rename-form
|
||||
display: none
|
||||
.outfit-rename-button
|
||||
+opacity(.75)
|
||||
font-size: 75%
|
||||
margin-left: 1em
|
||||
.outfit-url
|
||||
+opacity(.5)
|
||||
=sidebar-navbar-unselected
|
||||
background: transparent
|
||||
border-width: 0
|
||||
width: $outfit-content-inner-width
|
||||
&:hover
|
||||
+opacity(1)
|
||||
border-width: 1px
|
||||
.outfit-delete-confirmation
|
||||
display: none
|
||||
font-size: 75%
|
||||
span
|
||||
color: red
|
||||
a
|
||||
margin: 0 .25em
|
||||
&.active
|
||||
background: $module-bg-color
|
||||
&.confirming-deletion
|
||||
.outfit-delete
|
||||
visibility: hidden
|
||||
.outfit-url
|
||||
display: none
|
||||
.outfit-delete-confirmation
|
||||
display: block
|
||||
&.renaming
|
||||
h4
|
||||
display: none
|
||||
.outfit-rename-form
|
||||
display: inline
|
||||
&:hover
|
||||
.outfit-rename-button
|
||||
display: none
|
||||
&:hover
|
||||
.outfit-rename-button
|
||||
display: inline
|
||||
border-bottom: 1px solid $soft-border-color
|
||||
font-weight: normal
|
||||
|
||||
=sidebar-navbar-selected
|
||||
background: white
|
||||
border-bottom-color: white
|
||||
font-weight: bold
|
||||
|
||||
=sidebar-view-child
|
||||
margin:
|
||||
|
@ -144,7 +80,7 @@ body.outfits-edit
|
|||
position: left center
|
||||
repeat: no-repeat
|
||||
padding-left: 20px
|
||||
#save-outfit, #save-outfit-not-signed-in, #save-current-outfit, #save-outfit-copy, #save-outfit-finish
|
||||
#save-outfit, #save-outfit-not-signed-in, #save-current-outfit, #save-outfit-finish
|
||||
+loud-awesome-button-color
|
||||
#current-outfit-permalink, #shared-outfit-permalink
|
||||
display: none
|
||||
|
@ -207,11 +143,14 @@ body.outfits-edit
|
|||
&.image-active
|
||||
#preview-mode-image
|
||||
+active-mode
|
||||
#report-broken-image
|
||||
#preview-mode-note, #report-broken-image
|
||||
display: block
|
||||
&.can-download
|
||||
#preview-download-image
|
||||
display: inline-block
|
||||
// Phasing out the image download section. Not confident enough yet to
|
||||
// *remove* it, depending on user feedback, but that's a TODO for down
|
||||
// the road if hiding goes well.
|
||||
// &.can-download
|
||||
// #preview-download-image
|
||||
// display: inline-block
|
||||
#preview-mode-toggle
|
||||
+border-radius(.5em)
|
||||
border: 1px solid $module-border-color
|
||||
|
@ -249,34 +188,27 @@ body.outfits-edit
|
|||
em
|
||||
font-style: normal
|
||||
text-decoration: underline
|
||||
#report-broken-image
|
||||
#preview-mode-note, #report-broken-image
|
||||
display: none
|
||||
|
||||
#preview-sidebar
|
||||
+border-radius(10px)
|
||||
border: 1px solid $soft-border-color
|
||||
float: left
|
||||
height: $preview-dimension
|
||||
margin-left: $sidebar-margin
|
||||
margin-bottom: 1em
|
||||
overflow: auto
|
||||
width: $container_width - $preview-dimension - $sidebar-margin - 2px
|
||||
width: $container_width - $preview-dimension - $sidebar-margin
|
||||
&.viewing-outfits
|
||||
#preview-closet
|
||||
display: none
|
||||
#preview-outfits
|
||||
display: block
|
||||
&.viewing-saving-outfit
|
||||
height: auto
|
||||
max-height: 100%
|
||||
&.sharing
|
||||
#preview-closet
|
||||
display: none
|
||||
#preview-saving-outfit
|
||||
#preview-sharing
|
||||
display: block
|
||||
.sidebar-view
|
||||
h2
|
||||
margin:
|
||||
bottom: .25em
|
||||
left: $sidebar-unit-horizontal-padding
|
||||
margin: 1.5em 0
|
||||
#preview-closet
|
||||
h2
|
||||
margin-bottom: 0
|
||||
|
@ -378,7 +310,6 @@ body.outfits-edit
|
|||
width: 100%
|
||||
#preview-sidebar
|
||||
float: right
|
||||
height: 100%
|
||||
margin: 0
|
||||
position: relative
|
||||
width: $sidebar-width
|
||||
|
@ -461,20 +392,284 @@ body.outfits-edit
|
|||
#preview-outfits
|
||||
display: none
|
||||
text-align: left
|
||||
|
||||
$outfit-inner-size: 110px
|
||||
$outfit-margin: 1px
|
||||
$outfit-outer-size: $outfit-inner-size + ($outfit-margin * 2)
|
||||
> ul
|
||||
+outfits-list
|
||||
+sidebar-view-child
|
||||
background: image-url("loading.gif") no-repeat center top
|
||||
display: block
|
||||
display: none
|
||||
font-family: $main-font
|
||||
list-style: none
|
||||
margin:
|
||||
bottom: 1em
|
||||
margin: 0 auto 1em
|
||||
min-height: 16px
|
||||
> li
|
||||
+outfit
|
||||
width: $outfit-outer-size * 3
|
||||
|
||||
&.loaded
|
||||
background: transparent
|
||||
|
||||
> li
|
||||
height: $outfit-inner-size
|
||||
margin: $outfit-margin
|
||||
width: $outfit-inner-size
|
||||
|
||||
$outfit-header-h-padding: 4px
|
||||
$outfit-header-v-padding: 2px
|
||||
$outfit-header-inner-width: $outfit-inner-size - (2 * $outfit-header-h-padding)
|
||||
$outfit-header-inner-height: 12px
|
||||
$outfit-header-outer-height: $outfit-header-inner-height + (2 * $outfit-header-v-padding)
|
||||
header, footer, .outfit-delete-confirmation
|
||||
font-size: $outfit-header-inner-height
|
||||
padding: $outfit-header-v-padding $outfit-header-h-padding
|
||||
width: $outfit-header-inner-width
|
||||
|
||||
header
|
||||
+opacity(0.75)
|
||||
bottom: 0
|
||||
cursor: pointer
|
||||
|
||||
footer, .outfit-delete-confirmation
|
||||
display: none
|
||||
|
||||
.outfit-delete-confirmation
|
||||
+outfit-banner
|
||||
+outfit-banner-background(rgb(255, 50, 50))
|
||||
text-align: center
|
||||
top: 0
|
||||
|
||||
span
|
||||
font-weight: bold
|
||||
|
||||
$outfit-thumbnail-size: 150px
|
||||
$outfit-thumbnail-h-offset: ($outfit-inner-size - $outfit-thumbnail-size) / 2
|
||||
$outfit-thumbnail-v-offset: $outfit-thumbnail-h-offset - ($outfit-header-outer-height / 4)
|
||||
.outfit-thumbnail-wrapper
|
||||
+opacity(.5)
|
||||
background:
|
||||
image: url(/images/outfits/small_default.png)
|
||||
position: center center
|
||||
size: $outfit-inner-size $outfit-inner-size
|
||||
cursor: pointer
|
||||
height: $outfit-thumbnail-size
|
||||
left: $outfit-thumbnail-h-offset
|
||||
position: absolute
|
||||
top: $outfit-thumbnail-v-offset
|
||||
width: $outfit-thumbnail-size
|
||||
z-index: 1
|
||||
|
||||
.outfit-thumbnail
|
||||
display: none
|
||||
|
||||
.outfit-star
|
||||
bottom: 0
|
||||
margin-right: 4px
|
||||
|
||||
.outfit-delete
|
||||
float: right
|
||||
|
||||
.outfit-rename-button
|
||||
float: left
|
||||
|
||||
.outfit-rename-button, .outfit-delete
|
||||
font-size: 85%
|
||||
text-decoration: none
|
||||
|
||||
&:hover
|
||||
text-decoration: underline
|
||||
|
||||
.outfit-rename-form
|
||||
display: none
|
||||
|
||||
input
|
||||
background: transparent
|
||||
border: 1px solid white
|
||||
width: 6em
|
||||
|
||||
&:hover
|
||||
header
|
||||
+opacity(1)
|
||||
|
||||
.outfit-thumbnail
|
||||
+opacity(0.75)
|
||||
|
||||
footer
|
||||
display: block
|
||||
|
||||
&.active
|
||||
header
|
||||
+opacity(1)
|
||||
font-weight: bold
|
||||
|
||||
.outfit-thumbnail
|
||||
+opacity(1)
|
||||
|
||||
&.confirming-deletion
|
||||
footer
|
||||
display: none
|
||||
|
||||
.outfit-delete-confirmation
|
||||
display: block
|
||||
|
||||
&.renaming
|
||||
.outfit-name
|
||||
display: none
|
||||
|
||||
.outfit-rename-form
|
||||
display: inline
|
||||
|
||||
&.thumbnail-available
|
||||
background: transparent
|
||||
|
||||
.outfit-thumbnail-wrapper
|
||||
background-image: none
|
||||
|
||||
.outfit-thumbnail
|
||||
display: block
|
||||
|
||||
&.loading
|
||||
.outfit-star
|
||||
background-image: image-url("loading_outfit_pane.gif")
|
||||
|
||||
#preview-outfits-not-logged-in
|
||||
text-align: center
|
||||
overflow-x: hidden
|
||||
|
||||
img
|
||||
border:
|
||||
color: $module-border-color
|
||||
style: solid
|
||||
width: 1px 0
|
||||
|
||||
figure
|
||||
display: block
|
||||
margin: 0 0 1em 0
|
||||
padding: 0
|
||||
|
||||
figcaption
|
||||
display: block
|
||||
font-weight: bold
|
||||
|
||||
p
|
||||
+sidebar-view-child
|
||||
font-size: 85%
|
||||
|
||||
#preview-outfits-log-in
|
||||
+awesome-button
|
||||
+loud-awesome-button-color
|
||||
|
||||
#preview-sharing
|
||||
display: none
|
||||
|
||||
#preview-sharing-urls
|
||||
+sidebar-view-child
|
||||
display: none
|
||||
margin:
|
||||
bottom: 1em
|
||||
top: 1em
|
||||
|
||||
li
|
||||
display: block
|
||||
padding: .25em 0
|
||||
width: 100%
|
||||
|
||||
label
|
||||
display: block
|
||||
font-weight: bold
|
||||
|
||||
input
|
||||
display: block
|
||||
width: 100%
|
||||
|
||||
#preview-sharing-url-formats
|
||||
+sidebar-view-child
|
||||
+user-select(none)
|
||||
// remove whitespace between inline-block elements
|
||||
display: none
|
||||
font-size: 0
|
||||
text-align: center
|
||||
|
||||
li
|
||||
+inline-block
|
||||
|
||||
border: 1px solid $module-border-color
|
||||
border-left-width: 0
|
||||
border-right-color: $soft-border-color
|
||||
color: $soft-text-color
|
||||
cursor: pointer
|
||||
font-size: 12px
|
||||
padding: 0 2em
|
||||
|
||||
&.active
|
||||
background: $module-bg-color
|
||||
color: inherit
|
||||
font-weight: bold
|
||||
|
||||
&:first-child
|
||||
+border-top-left-radius(5px)
|
||||
+border-bottom-left-radius(5px)
|
||||
border-left-width: 1px
|
||||
|
||||
&:last-child
|
||||
+border-top-right-radius(5px)
|
||||
+border-bottom-right-radius(5px)
|
||||
border-right-color: $module-border-color
|
||||
|
||||
#preview-sharing-thumbnail-wrapper
|
||||
border: 1px solid $soft-border-color
|
||||
display: block
|
||||
height: 150px
|
||||
margin: 1em auto 0
|
||||
position: relative
|
||||
width: 150px
|
||||
|
||||
#preview-sharing-thumbnail-loading
|
||||
height: 100%
|
||||
left: 0
|
||||
position: absolute
|
||||
top: 0
|
||||
width: 100%
|
||||
|
||||
span
|
||||
color: $soft-text-color
|
||||
font-size: 85%
|
||||
margin-top: -0.75em
|
||||
position: absolute
|
||||
text-align: center
|
||||
top: 50%
|
||||
width: 100%
|
||||
|
||||
#preview-sharing-thumbnail, #preview-sharing-thumbnail-generating
|
||||
display: none
|
||||
|
||||
#preview-sharing-beta-note
|
||||
+sidebar-view-child
|
||||
+warning
|
||||
font-size: 85%
|
||||
margin-top: 1em
|
||||
text-align: center
|
||||
|
||||
&.urls-loaded
|
||||
#preview-sharing-thumbnail-saving
|
||||
display: none
|
||||
|
||||
#preview-sharing-urls, #preview-sharing-url-formats, #preview-sharing-thumbnail-generating
|
||||
display: block
|
||||
|
||||
&.urls-loaded.thumbnail-loaded
|
||||
#preview-sharing-thumbnail-loading
|
||||
display: none
|
||||
|
||||
#preview-sharing-thumbnail
|
||||
display: block
|
||||
|
||||
&.urls-loaded.thumbnail-available
|
||||
#preview-sharing-thumbnail-loading
|
||||
+opacity(0.85)
|
||||
|
||||
#preview-sharing-thumbnail
|
||||
display: block
|
||||
|
||||
.preview-sidebar-nav
|
||||
float: right
|
||||
font-size: 85%
|
||||
|
@ -482,6 +677,49 @@ body.outfits-edit
|
|||
right: $sidebar-unit-horizontal-padding
|
||||
top: 1em
|
||||
|
||||
$sidebar-border-radius: 10px
|
||||
$sidebar-navbar-inner-width: $sidebar-width - 2px
|
||||
$sidebar-navbar-child-outer-width: floor($sidebar-navbar-inner-width / 3)
|
||||
|
||||
#preview-sidebar
|
||||
#preview-sidebar-navbar-closet
|
||||
+sidebar-navbar-selected
|
||||
|
||||
&.viewing-outfits, &.sharing
|
||||
#preview-sidebar-navbar-closet
|
||||
+sidebar-navbar-unselected
|
||||
|
||||
&.viewing-outfits #preview-sidebar-navbar-outfits, &.sharing #preview-sidebar-navbar-sharing
|
||||
+sidebar-navbar-selected
|
||||
|
||||
#preview-sidebar-navbar
|
||||
+border-radius($sidebar-border-radius $sidebar-border-radius 0 0)
|
||||
+clearfix
|
||||
+header-text
|
||||
background: $module-bg-color
|
||||
border: 1px solid $soft-border-color
|
||||
border-bottom: 0
|
||||
font-size: 150%
|
||||
|
||||
> div
|
||||
+sidebar-navbar-unselected
|
||||
cursor: pointer
|
||||
float: left
|
||||
border-left: 1px solid $soft-border-color
|
||||
padding: .5em 0
|
||||
text-align: center
|
||||
width: $sidebar-navbar-child-outer-width
|
||||
|
||||
&:first-child
|
||||
border-left: 0
|
||||
|
||||
#preview-sidebar-content
|
||||
+border-radius(0 0 $sidebar-border-radius $sidebar-border-radius)
|
||||
border: 1px solid $soft-border-color
|
||||
border-top: 0
|
||||
height: 300px
|
||||
overflow: auto
|
||||
|
||||
#save-success, #save-error, #outfit-not-found, #preview-sidebar-donation-request
|
||||
+sidebar-view-child
|
||||
display: none
|
||||
|
@ -507,25 +745,10 @@ body.outfits-edit
|
|||
+opacity(.5)
|
||||
display: none
|
||||
|
||||
#new-outfit
|
||||
+outfit
|
||||
+sidebar-view-child
|
||||
display: none
|
||||
h4
|
||||
display: inline
|
||||
&:hover
|
||||
text-decoration: none
|
||||
.outfit-star
|
||||
margin-top: .5em
|
||||
|
||||
#new-outfit-name
|
||||
font: inherit
|
||||
line-height: 1
|
||||
|
||||
#preview-saving-outfit
|
||||
display: none
|
||||
padding-bottom: 1em
|
||||
|
||||
#pet-type-form, #pet-state-form, #preview-swf, #preview-search-form
|
||||
position: relative
|
||||
|
||||
|
@ -541,7 +764,7 @@ body.outfits-edit
|
|||
display: none
|
||||
|
||||
form#save-outfit-form
|
||||
+outfit
|
||||
+outfit-star-shifted
|
||||
display: none
|
||||
margin-right: 0
|
||||
padding: 0
|
||||
|
@ -572,8 +795,11 @@ body.outfits-edit
|
|||
display: none
|
||||
#save-current-outfit, #save-outfit-copy
|
||||
display: inline-block
|
||||
#current-outfit-permalink
|
||||
display: inline-block
|
||||
// Phasing out permalink. Shared outfit links have been straight-up
|
||||
// removed, but this may stay depending on user feedback. Otherwise,
|
||||
// removing it is TODO down the road.
|
||||
// #current-outfit-permalink
|
||||
// display: inline-block
|
||||
&.saving-outfit
|
||||
#save-outfit-form
|
||||
display: block
|
||||
|
@ -581,6 +807,10 @@ body.outfits-edit
|
|||
display: none
|
||||
.preview-search-form-your-items
|
||||
+inline-block
|
||||
#preview-outfits-not-logged-in
|
||||
display: none
|
||||
#preview-outfits-list
|
||||
display: block
|
||||
|
||||
&.user-not-signed-in
|
||||
#save-outfit-not-signed-in
|
||||
|
|
|
@ -1,27 +1,55 @@
|
|||
@import "partials/outfit"
|
||||
@import star
|
||||
|
||||
$outfit-inner-height: 150px
|
||||
$outfit-inner-width: 150px
|
||||
$outfit-banner-h-padding: 4px
|
||||
$outfit-banner-v-padding: 2px
|
||||
$outfit-banner-inner-width: $outfit-inner-width - (2 * $outfit-banner-h-padding)
|
||||
|
||||
body.outfits-index
|
||||
#outfits
|
||||
list-style: none
|
||||
+outfits-list
|
||||
|
||||
li
|
||||
+outfit-star
|
||||
clear: left
|
||||
float: left
|
||||
margin-bottom: .5em
|
||||
> li
|
||||
height: $outfit-inner-height
|
||||
margin: 2px
|
||||
width: $outfit-inner-width
|
||||
|
||||
h4
|
||||
float: left
|
||||
width: 12em
|
||||
header, footer
|
||||
padding: $outfit-banner-v-padding $outfit-banner-h-padding
|
||||
width: $outfit-banner-inner-width
|
||||
|
||||
.outfit-edit-link, form
|
||||
float: left
|
||||
font-size: 85%
|
||||
margin-left: 1em
|
||||
footer
|
||||
display: none
|
||||
|
||||
.outfit-edit-link
|
||||
+awesome-button
|
||||
float: left
|
||||
text-decoration: none
|
||||
|
||||
form
|
||||
float: right
|
||||
|
||||
.outfit-delete-button
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
.outfit-edit-link, .outfit-delete-button
|
||||
&:hover
|
||||
text-decoration: underline
|
||||
|
||||
.outfit-star
|
||||
cursor: auto
|
||||
|
||||
.outfit-name
|
||||
text-decoration: none
|
||||
|
||||
&:hover
|
||||
text-decoration: underline
|
||||
|
||||
&:hover
|
||||
footer
|
||||
display: block
|
||||
|
||||
.outfit-delete-button
|
||||
+reset-awesome-button
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
background-image: image-url("star.png")
|
||||
&.loading .outfit-star
|
||||
background-image: image-url("loading.gif")
|
||||
&.loading.active .outfit-star
|
||||
background-image: image-url("loading_current_outfit.gif")
|
||||
|
||||
=outfit-star-shifted
|
||||
+outfit-star
|
||||
|
|
37
app/stylesheets/partials/_outfit.sass
Normal file
|
@ -0,0 +1,37 @@
|
|||
=outfit
|
||||
+inline-block
|
||||
+outfit-star
|
||||
overflow: hidden
|
||||
position: relative
|
||||
|
||||
header, footer
|
||||
+outfit-banner
|
||||
+outfit-banner-background(black)
|
||||
|
||||
header
|
||||
bottom: 0
|
||||
|
||||
footer
|
||||
top: 0
|
||||
|
||||
a
|
||||
color: white
|
||||
|
||||
=outfits-list
|
||||
// remove whitespace between inline-block elements
|
||||
font-size: 0
|
||||
list-style: none
|
||||
|
||||
> li
|
||||
+outfit
|
||||
font-size: 14px
|
||||
|
||||
=outfit-banner
|
||||
color: white
|
||||
left: 0
|
||||
position: absolute
|
||||
z-index: 2
|
||||
|
||||
=outfit-banner-background($color)
|
||||
background: $color
|
||||
background: rgba($color, 0.75)
|
|
@ -12,7 +12,7 @@
|
|||
%ul#report-assets
|
||||
- @swf_assets.each do |swf_asset|
|
||||
%li
|
||||
= link_to image_tag(swf_asset.s3_url([150, 150])), swf_asset.url
|
||||
= link_to image_tag(swf_asset.image_url([150, 150])), swf_asset.url
|
||||
- unless swf_asset.image_pending_repair?
|
||||
= form_tag(:action => :create) do
|
||||
= hidden_field_tag 'swf_asset_remote_id', swf_asset.remote_id
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
= yield :stylesheets
|
||||
= stylesheet_link_tag "compiled/screen"
|
||||
= yield :meta
|
||||
= open_graph_tags
|
||||
= csrf_meta_tag
|
||||
= signed_in_meta_tag
|
||||
%body{:class => body_class}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
= outfit_li_for(outfit) do
|
||||
.outfit-star
|
||||
%h4= link_to outfit.name, outfit
|
||||
= link_to_edit_outfit 'Edit', outfit, :class => 'outfit-edit-link'
|
||||
= button_to('Delete', outfit, :method => 'delete', :class => 'outfit-delete-button', :confirm => "Are you sure you want to delete the outfit #{outfit.name}?")
|
||||
- if outfit.image?
|
||||
= link_to image_tag(outfit.image.small.url), outfit
|
||||
|
||||
%header
|
||||
.outfit-star
|
||||
= link_to outfit.name, outfit, :class => 'outfit-name'
|
||||
|
||||
%footer
|
||||
= link_to_edit_outfit 'edit', outfit, :class => 'outfit-edit-link'
|
||||
= button_to 'delete', outfit, :method => 'delete', :class => 'outfit-delete-button', :confirm => "Are you sure you want to delete the outfit #{outfit.name}?"
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -17,14 +17,10 @@
|
|||
#save-outfit-wrapper
|
||||
%a#current-outfit-permalink{:target => '_blank'}
|
||||
= image_tag 'link_go.png', :alt => 'Permalink', :title => 'Permalink to current outfit'
|
||||
%a#shared-outfit-permalink{:target => '_blank'}
|
||||
= image_tag 'link_go.png', :alt => 'Permalink', :title => 'Permalink to shared outfit'
|
||||
%input#shared-outfit-url.outfit-url{:type => 'text'}
|
||||
%button#share-outfit Share outfit
|
||||
%button#save-outfit Save outfit
|
||||
%button#save-outfit-not-signed-in Log in to save
|
||||
%button#save-outfit-copy Save as…
|
||||
%button#save-current-outfit Save "<span>current outfit</span>"
|
||||
%button#save-outfit-copy Save a copy
|
||||
%form#save-outfit-form
|
||||
.outfit-star
|
||||
%input#save-outfit-name{:type => 'text', :placeholder => 'Outfit name'}
|
||||
|
@ -62,30 +58,67 @@
|
|||
%em donate
|
||||
at least $5 to help upgrade the server. Thanks!
|
||||
#preview-sidebar
|
||||
%nav#preview-sidebar-navbar
|
||||
#preview-sidebar-navbar-closet Closet
|
||||
#preview-sidebar-navbar-sharing Sharing
|
||||
#preview-sidebar-navbar-outfits Outfits
|
||||
#preview-sidebar-content
|
||||
#outfit-not-found Outfit not found
|
||||
#save-success Outfit successfully saved
|
||||
#save-error
|
||||
#preview-closet.sidebar-view
|
||||
%a#preview-sidebar-nav-outfits.preview-sidebar-nav{:href => '#'} Your outfits
|
||||
%h2 Closet
|
||||
%ul
|
||||
%p#fullscreen-copyright
|
||||
Images © 2000-2010 Neopets, Inc. All Rights Reserved.
|
||||
Used With Permission
|
||||
#preview-outfits.sidebar-view
|
||||
%a#preview-sidebar-nav-closet.preview-sidebar-nav{:href => "#"} ← Back to Closet
|
||||
%h2 Your outfits
|
||||
%ul
|
||||
#preview-saving-outfit.sidebar-view
|
||||
%a#preview-sidebar-nav-cancel-save.preview-sidebar-nav{:href => '#'} ← Cancel
|
||||
%h2 Saving new outfit
|
||||
#new-outfit
|
||||
%form#new-outfit-form
|
||||
%header
|
||||
.outfit-star
|
||||
%h4
|
||||
%input#new-outfit-name{:type => 'text', :placeholder => 'Outfit name'}
|
||||
%button{:type => 'submit'} Save
|
||||
%ul#preview-outfits-list
|
||||
#preview-outfits-not-logged-in
|
||||
%figure
|
||||
= image_tag 'outfits_welcome.png'
|
||||
%figcaption Ready to become a pro designer?
|
||||
:markdown
|
||||
We know how hard it can be to keep track of your ideas,
|
||||
especially if you end up having a lot of them.
|
||||
**But Dress to Impress makes it easy.**
|
||||
|
||||
Once you have an idea for an outfit, you can **build it,
|
||||
save it, and view it again later**, either to update your
|
||||
design or finally make your dream a reality.
|
||||
|
||||
**Thousands of users have already saved tens of thousands of
|
||||
outfits — will you be next?**
|
||||
|
||||
= link_to 'Log in to save this outfit', login_path_with_return_to, :id => 'preview-outfits-log-in'
|
||||
#preview-sharing.sidebar-view
|
||||
#preview-sharing-thumbnail-wrapper
|
||||
#preview-sharing-thumbnail-loading
|
||||
= image_tag 'outfits/small_loading.gif'
|
||||
%span#preview-sharing-thumbnail-saving Saving…
|
||||
%span#preview-sharing-thumbnail-generating Generating…
|
||||
%img#preview-sharing-thumbnail
|
||||
%p#preview-sharing-beta-note
|
||||
We're currently beta testing outfit image generation. It might be
|
||||
slow or not work at all, and we might have to take it down.
|
||||
Still, we're really excited about this feature, and we hope you
|
||||
are, too!
|
||||
%ul#preview-sharing-urls
|
||||
%li
|
||||
%label{:for => 'preview-sharing-permalink-url'} Outfit page
|
||||
%input#preview-sharing-permalink-url.outfit-url{:type => 'text'}
|
||||
%li
|
||||
%label{:for => 'preview-sharing-large-image-url'} Large image
|
||||
%input#preview-sharing-large-image-url.outfit-url{:type => 'text'}
|
||||
%li
|
||||
%label{:for => 'preview-sharing-medium-image-url'} Medium image
|
||||
%input#preview-sharing-medium-image-url.outfit-url{:type => 'text'}
|
||||
%li
|
||||
%label{:for => 'preview-sharing-small-image-url'} Small image
|
||||
%input#preview-sharing-small-image-url.outfit-url{:type => 'text'}
|
||||
%ul#preview-sharing-url-formats
|
||||
%li.active{'data-format' => 'plain'} Plain
|
||||
%li{'data-format' => 'html'} HTML
|
||||
%li{'data-format' => 'bbcode'} BBCode
|
||||
%form#preview-search-form
|
||||
%header
|
||||
%h2 Add an item
|
||||
|
@ -132,18 +165,20 @@
|
|||
%script#outfit-template{:type => 'text/x-jquery-tmpl'}
|
||||
<li class="outfit-${id}{{if starred}} starred{{/if}}">
|
||||
%header
|
||||
%button.outfit-delete ×
|
||||
.outfit-star
|
||||
%h4 ${name}
|
||||
%a.outfit-rename-button{:href => '#'} rename
|
||||
%span.outfit-name ${name}
|
||||
%form.outfit-rename-form
|
||||
%input.outfit-rename-field{:type => 'text'}
|
||||
%input.outfit-url{:type => 'text', :value => "http://#{request.host}/outfits/${id}"}
|
||||
%footer
|
||||
%a.outfit-rename-button{:href => '#'} rename
|
||||
%a.outfit-delete{:href => '#'} delete
|
||||
.outfit-thumbnail-wrapper
|
||||
%img.outfit-thumbnail
|
||||
.outfit-delete-confirmation
|
||||
%span Delete forever?
|
||||
%span Delete?
|
||||
%a.outfit-delete-confirmation-yes{:href => '#'} yes
|
||||
\/
|
||||
%a.outfit-delete-confirmation-no{:href => '#'} no, thanks
|
||||
%a.outfit-delete-confirmation-no{:href => '#'} no
|
||||
</li>
|
||||
- content_for :javascripts do
|
||||
= include_javascript_libraries :jquery, :swfobject, :jquery_tmpl
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
- title(@outfit.name || "Shared outfit")
|
||||
- content_for :before_title, campaign_progress
|
||||
|
||||
- open_graph :type => 'openneo-impress:outfit', :title => yield(:title),
|
||||
:url => outfit_url(@outfit)
|
||||
- if @outfit.image?
|
||||
- open_graph :image => absolute_url(@outfit.image.url)
|
||||
|
||||
= link_to_edit_outfit(@outfit, :class => 'button', :id => 'outfit-wardrobe-link') do
|
||||
Edit
|
||||
- unless user_signed_in? && @outfit.user == current_user
|
||||
|
|
3
config/initializers/asset_hosts.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
ASSET_HOSTS = {
|
||||
:swf_asset_images => 'd1i4vx4g4uxw7j.cloudfront.net'
|
||||
}
|
20
config/initializers/carrierwave.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
# By default, we'll have CarrierWave use S3 only on production. (Since each
|
||||
# asset image has only One True Image no matter the environment, we'll override
|
||||
# this to use S3 on all environments for those images only.)
|
||||
|
||||
CarrierWave.configure do |config|
|
||||
if Rails.env.production?
|
||||
s3_config = YAML.load_file Rails.root.join('config', 'aws_s3.yml')
|
||||
access_key_id = s3_config['access_key_id']
|
||||
secret_access_key = s3_config['secret_access_key']
|
||||
|
||||
config.storage = :fog
|
||||
config.fog_credentials = {
|
||||
:provider => 'AWS',
|
||||
:aws_access_key_id => access_key_id,
|
||||
:aws_secret_access_key => secret_access_key
|
||||
}
|
||||
else
|
||||
config.storage = :file
|
||||
end
|
||||
end
|
9
db/migrate/20120308205324_add_image_to_outfits.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class AddImageToOutfits < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :outfits, :image, :string
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :outfits, :image
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class AddImageLayersHashToOutfit < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :outfits, :image_layers_hash, :string, :length => 8
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :outfits, :image_layers_hash
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class AddImageEnqueuedToOutfits < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :outfits, :image_enqueued, :boolean, :null => false, :default => false
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :outfits, :image_enqueued
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20120521164652) do
|
||||
ActiveRecord::Schema.define(:version => 20120725232903) do
|
||||
|
||||
create_table "auth_servers", :force => true do |t|
|
||||
t.string "short_name", :limit => 10, :null => false
|
||||
|
@ -118,6 +118,8 @@ ActiveRecord::Schema.define(:version => 20120521164652) do
|
|||
t.string "name"
|
||||
t.boolean "starred", :default => false, :null => false
|
||||
t.string "image"
|
||||
t.string "image_layers_hash"
|
||||
t.boolean "image_enqueued", :default => false, :null => false
|
||||
end
|
||||
|
||||
add_index "outfits", ["pet_state_id"], :name => "index_outfits_on_pet_state_id"
|
||||
|
|
11
lib/tasks/outfits.rake
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace :outfits do
|
||||
desc 'Retroactively enqueue image updates for outfits saved to user accounts'
|
||||
task :retroactively_enqueue => :environment do
|
||||
outfits = Outfit.select([:id]).where('image IS NULL AND user_id IS NOT NULL')
|
||||
puts "Enqueuing #{outfits.count} outfits"
|
||||
outfits.find_each do |outfit|
|
||||
Resque.enqueue(OutfitImageUpdate::Retroactive, outfit.id)
|
||||
end
|
||||
puts "Successfully enqueued."
|
||||
end
|
||||
end
|
BIN
public/images/loading_outfit_pane.gif
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/images/outfits/default.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/images/outfits/medium_default.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/images/outfits/small_default.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
public/images/outfits/small_loading.gif
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
public/images/outfits_welcome.png
Normal file
After Width: | Height: | Size: 76 KiB |
|
@ -57,7 +57,7 @@ Partial.ItemSet = function ItemSet(wardrobe, selector) {
|
|||
var item, no_assets, li, no_assets_message;
|
||||
for(var i = 0, l = specific_items.length; i < l; i++) {
|
||||
item = specific_items[i];
|
||||
no_assets = item.couldNotLoadAssetsFitting(wardrobe.outfit.getPetType());
|
||||
no_assets = item.couldNotLoadAssetsFitting(wardrobe.outfits.getPetType());
|
||||
li = $('li.object-' + item.id).toggleClass('no-assets', no_assets);
|
||||
(function (li) {
|
||||
no_assets_message = li.find('span.no-assets-message');
|
||||
|
@ -103,8 +103,8 @@ Partial.ItemSet = function ItemSet(wardrobe, selector) {
|
|||
}
|
||||
li.append(img).append(controls).append(info_link).append(item.name).appendTo(ul);
|
||||
}
|
||||
setClosetItems(wardrobe.outfit.getClosetItems());
|
||||
setOutfitItems(wardrobe.outfit.getWornItems());
|
||||
setClosetItems(wardrobe.outfits.getClosetItems());
|
||||
setOutfitItems(wardrobe.outfits.getWornItems());
|
||||
}
|
||||
|
||||
$('span.no-assets-message').live('mouseover', function () {
|
||||
|
@ -117,9 +117,9 @@ Partial.ItemSet = function ItemSet(wardrobe, selector) {
|
|||
no_assets_full_message.removeAttr('style');
|
||||
});
|
||||
|
||||
wardrobe.outfit.bind('updateItemAssets', function () { setHasAssets(wardrobe.outfit.getWornItems()) });
|
||||
wardrobe.outfit.bind('updateWornItems', setOutfitItems);
|
||||
wardrobe.outfit.bind('updateClosetItems', setClosetItems);
|
||||
wardrobe.outfits.bind('updateItemAssets', function () { setHasAssets(wardrobe.outfits.getWornItems()) });
|
||||
wardrobe.outfits.bind('updateWornItems', setOutfitItems);
|
||||
wardrobe.outfits.bind('updateClosetItems', setClosetItems);
|
||||
}
|
||||
|
||||
Partial.ItemSet.CONTROL_SETS = {};
|
||||
|
@ -151,12 +151,12 @@ Partial.ItemSet.setWardrobe = function (wardrobe) {
|
|||
}
|
||||
|
||||
toggle_fn.closeted = {};
|
||||
toggle_fn.closeted[true] = $.proxy(wardrobe.outfit, 'closetItem');
|
||||
toggle_fn.closeted[false] = $.proxy(wardrobe.outfit, 'unclosetItem');
|
||||
toggle_fn.closeted[true] = $.proxy(wardrobe.outfits, 'closetItem');
|
||||
toggle_fn.closeted[false] = $.proxy(wardrobe.outfits, 'unclosetItem');
|
||||
|
||||
toggle_fn.worn = {};
|
||||
toggle_fn.worn[true] = $.proxy(wardrobe.outfit, 'wearItem');
|
||||
toggle_fn.worn[false] = $.proxy(wardrobe.outfit, 'unwearItem');
|
||||
toggle_fn.worn[true] = $.proxy(wardrobe.outfits, 'wearItem');
|
||||
toggle_fn.worn[false] = $.proxy(wardrobe.outfits, 'unwearItem');
|
||||
|
||||
Partial.ItemSet.setWardrobe = $.noop;
|
||||
}
|
||||
|
@ -164,14 +164,16 @@ Partial.ItemSet.setWardrobe = function (wardrobe) {
|
|||
View.Closet = function (wardrobe) {
|
||||
var item_set = new Partial.ItemSet(wardrobe, '#preview-closet ul');
|
||||
|
||||
wardrobe.outfit.bind('updateClosetItems', $.proxy(item_set, 'setItems'));
|
||||
wardrobe.outfits.bind('updateClosetItems', $.proxy(item_set, 'setItems'));
|
||||
}
|
||||
|
||||
View.Fullscreen = function (wardrobe) {
|
||||
var full = $(document.body).hasClass('fullscreen'), win = $(window),
|
||||
preview_el = $('#preview'), search_el = $('#preview-search-form'),
|
||||
preview_swf = $('#preview-swf'), sidebar_el = $('#preview-sidebar'),
|
||||
footer = $('#footer'), jwindow = $(window), overrideFull = false;
|
||||
sidebar_content_el = $('#preview-sidebar-content'),
|
||||
sidebar_navbar_el = $('#preview-sidebar-navbar'), footer = $('#footer'),
|
||||
jwindow = $(window), overrideFull = false;
|
||||
|
||||
function fit() {
|
||||
if(!overrideFull) {
|
||||
|
@ -182,6 +184,7 @@ View.Fullscreen = function (wardrobe) {
|
|||
if(!full) {
|
||||
preview_swf.removeAttr('style').css('visibility', 'visible');
|
||||
preview_el.removeAttr('style');
|
||||
sidebar_content_el.removeAttr('style');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,6 +216,12 @@ View.Fullscreen = function (wardrobe) {
|
|||
preview_swf.css(size.next);
|
||||
|
||||
preview_el.height(available.height);
|
||||
|
||||
// Now that preview is fit, we fit the sidebar's content element, which
|
||||
// also has to deal with the constraint of its navbar's height.
|
||||
var sidebar_content_height = available.height -
|
||||
sidebar_navbar_el.outerHeight() - 1; // 1px bottom border
|
||||
sidebar_content_el.height(sidebar_content_height);
|
||||
}
|
||||
}
|
||||
$('#preview').data('fit', fit);
|
||||
|
@ -273,32 +282,32 @@ View.Hash = function (wardrobe) {
|
|||
}
|
||||
|
||||
if(new_data.color !== data.color || new_data.species !== data.species) {
|
||||
wardrobe.outfit.setPetTypeByColorAndSpecies(new_data.color, new_data.species);
|
||||
wardrobe.outfits.setPetTypeByColorAndSpecies(new_data.color, new_data.species);
|
||||
}
|
||||
if(new_data.closet) {
|
||||
if(!arraysMatch(new_data.closet, data.closet)) {
|
||||
wardrobe.outfit.setClosetItemsByIds(new_data.closet.slice(0));
|
||||
wardrobe.outfits.setClosetItemsByIds(new_data.closet.slice(0));
|
||||
}
|
||||
} else if(new_data.objects && !arraysMatch(new_data.objects, data.closet)) {
|
||||
wardrobe.outfit.setClosetItemsByIds(new_data.objects.slice(0));
|
||||
wardrobe.outfits.setClosetItemsByIds(new_data.objects.slice(0));
|
||||
} else {
|
||||
wardrobe.outfit.setClosetItemsByIds([]);
|
||||
wardrobe.outfits.setClosetItemsByIds([]);
|
||||
}
|
||||
if(new_data.objects) {
|
||||
if(!arraysMatch(new_data.objects, data.objects)) {
|
||||
wardrobe.outfit.setWornItemsByIds(new_data.objects.slice(0));
|
||||
wardrobe.outfits.setWornItemsByIds(new_data.objects.slice(0));
|
||||
}
|
||||
} else {
|
||||
wardrobe.outfit.setWornItemsByIds([]);
|
||||
wardrobe.outfits.setWornItemsByIds([]);
|
||||
}
|
||||
if(new_data.name != data.name && new_data.name) {
|
||||
wardrobe.base_pet.setName(new_data.name);
|
||||
}
|
||||
if(new_data.state != data.state) {
|
||||
wardrobe.outfit.setPetStateById(new_data.state);
|
||||
wardrobe.outfits.setPetStateById(new_data.state);
|
||||
}
|
||||
if(new_data.outfit != data.outfit) {
|
||||
wardrobe.outfit.setId(new_data.outfit);
|
||||
wardrobe.outfits.setId(new_data.outfit);
|
||||
}
|
||||
if(new_data.search != data.search || new_data.search_offset != data.search_offset) {
|
||||
wardrobe.search.setItemsByQuery(new_data.search, {offset: new_data.search_offset});
|
||||
|
@ -350,27 +359,27 @@ View.Hash = function (wardrobe) {
|
|||
}
|
||||
|
||||
function singleOutfitResponse(event_name, response) {
|
||||
wardrobe.outfit.bind(event_name, function () {
|
||||
if(!wardrobe.outfit.in_transaction) response.apply(this, arguments);
|
||||
wardrobe.outfits.bind(event_name, function () {
|
||||
if(!wardrobe.outfits.in_transaction) response.apply(this, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
singleOutfitResponse('updateClosetItems', function (items) {
|
||||
var item_ids = items.map('id');
|
||||
var item_ids = items.mapProperty('id');
|
||||
if(!arraysMatch(item_ids, data.closet)) {
|
||||
changeQuery({closet: item_ids});
|
||||
}
|
||||
});
|
||||
|
||||
singleOutfitResponse('updateWornItems', function (items) {
|
||||
var item_ids = items.map('id'), changes = {};
|
||||
var item_ids = items.mapProperty('id'), changes = {};
|
||||
if(!arraysMatch(item_ids, data.objects)) {
|
||||
changes.objects = item_ids;
|
||||
}
|
||||
if(arraysMatch(item_ids, data.closet) || arraysMatch(item_ids, data.objects)) {
|
||||
changes.closet = undefined;
|
||||
} else {
|
||||
changes.closet = wardrobe.outfit.getClosetItems().map('id');
|
||||
changes.closet = wardrobe.outfits.getClosetItems().mapProperty('id');
|
||||
}
|
||||
if(changes.objects || changes.closet) changeQuery(changes);
|
||||
});
|
||||
|
@ -390,7 +399,7 @@ View.Hash = function (wardrobe) {
|
|||
});
|
||||
|
||||
singleOutfitResponse('updatePetState', function (pet_state) {
|
||||
var pet_type = wardrobe.outfit.getPetType();
|
||||
var pet_type = wardrobe.outfits.getPetType();
|
||||
if(pet_state.id != data.state && pet_type && (data.state || pet_state.id != pet_type.pet_state_ids[0])) {
|
||||
changeQuery({state: pet_state.id});
|
||||
}
|
||||
|
@ -402,7 +411,7 @@ View.Hash = function (wardrobe) {
|
|||
}
|
||||
});
|
||||
|
||||
wardrobe.outfit.bind('loadOutfit', function (outfit) {
|
||||
wardrobe.outfits.bind('loadOutfit', function (outfit) {
|
||||
changeQuery({
|
||||
closet: outfit.getClosetItemIds(),
|
||||
color: outfit.pet_type.color_id,
|
||||
|
@ -413,7 +422,7 @@ View.Hash = function (wardrobe) {
|
|||
});
|
||||
});
|
||||
|
||||
wardrobe.outfit.bind('outfitNotFound', function (outfit) {
|
||||
wardrobe.outfits.bind('outfitNotFound', function (outfit) {
|
||||
var new_id = outfit ? outfit.id : undefined;
|
||||
changeQuery({outfit: new_id});
|
||||
});
|
||||
|
@ -430,8 +439,6 @@ View.Hash = function (wardrobe) {
|
|||
|
||||
View.Outfits = function (wardrobe) {
|
||||
var current_outfit_permalink_el = $('#current-outfit-permalink'),
|
||||
shared_outfit_permalink_el = $('#shared-outfit-permalink'),
|
||||
shared_outfit_url_el = $('#shared-outfit-url'),
|
||||
new_outfit_form_el = $('#save-outfit-form'),
|
||||
new_outfit_name_el = $('#save-outfit-name'),
|
||||
outfits_el = $('#preview-outfits'),
|
||||
|
@ -451,13 +458,6 @@ View.Outfits = function (wardrobe) {
|
|||
return $('li.outfit-' + outfit.id);
|
||||
}
|
||||
|
||||
function navLinkTo(callback) {
|
||||
return function (e) {
|
||||
e.preventDefault();
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(will_be_viewing) {
|
||||
var currently_viewing = sidebar_el.attr('class');
|
||||
if(currently_viewing != will_be_viewing) previously_viewing = currently_viewing;
|
||||
|
@ -476,14 +476,21 @@ View.Outfits = function (wardrobe) {
|
|||
/* Nav */
|
||||
|
||||
function showCloset() {
|
||||
sharing.onHide();
|
||||
navigateTo('');
|
||||
}
|
||||
|
||||
function showOutfits() {
|
||||
wardrobe.user.loadOutfits();
|
||||
sharing.onHide();
|
||||
wardrobe.outfits.loadOutfits();
|
||||
navigateTo('viewing-outfits');
|
||||
}
|
||||
|
||||
function showSharing() {
|
||||
sharing.onShow();
|
||||
navigateTo('sharing');
|
||||
}
|
||||
|
||||
function showNewOutfitForm() {
|
||||
new_outfit_name_el.val('');
|
||||
new_outfit_form_el.removeClass('starred').stopLoading();
|
||||
|
@ -495,9 +502,13 @@ View.Outfits = function (wardrobe) {
|
|||
save_outfit_wrapper_el.removeClass('saving-outfit');
|
||||
}
|
||||
|
||||
$('#preview-sidebar-nav-outfits').click(navLinkTo(showOutfits));
|
||||
|
||||
$('#preview-sidebar-nav-closet').click(navLinkTo(showCloset));
|
||||
$('#preview-sidebar-navbar-closet').click(showCloset);
|
||||
$('#preview-sidebar-navbar-sharing').click(function () {
|
||||
sharing.startLoading();
|
||||
wardrobe.outfits.share();
|
||||
showSharing();
|
||||
});
|
||||
$('#preview-sidebar-navbar-outfits').click(showOutfits);
|
||||
|
||||
$('#save-outfit, #save-outfit-copy').click(showNewOutfitForm);
|
||||
|
||||
|
@ -509,39 +520,64 @@ View.Outfits = function (wardrobe) {
|
|||
|
||||
/* Outfits list */
|
||||
|
||||
var list_image_subscriptions = {};
|
||||
|
||||
function listSubscribeToImage(outfit) {
|
||||
list_image_subscriptions[outfit.id] = wardrobe.image_subscriptions.subscribe(outfit);
|
||||
}
|
||||
|
||||
function listUnsubscribeFromImage(outfit) {
|
||||
if(outfit.id in list_image_subscriptions) {
|
||||
if(list_image_subscriptions[outfit.id] !== null) {
|
||||
wardrobe.image_subscriptions.unsubscribe(list_image_subscriptions[outfit.id]);
|
||||
}
|
||||
|
||||
delete list_image_subscriptions[outfit.id];
|
||||
}
|
||||
}
|
||||
|
||||
$('#outfit-template').template('outfitTemplate');
|
||||
|
||||
wardrobe.user.bind('outfitsLoaded', function (outfits) {
|
||||
wardrobe.outfits.bind('outfitsLoaded', function (outfits) {
|
||||
var outfit_els = $.tmpl('outfitTemplate', outfits);
|
||||
outfits_list_el.html('').append(outfit_els).addClass('loaded');
|
||||
updateActiveOutfit();
|
||||
|
||||
for(var i = 0; i < outfits.length; i++) {
|
||||
listSubscribeToImage(outfits[i]);
|
||||
}
|
||||
});
|
||||
|
||||
wardrobe.user.bind('addOutfit', function (outfit, i) {
|
||||
wardrobe.outfits.bind('addOutfit', function (outfit, i) {
|
||||
var next_child = outfits_list_el.children().not('.hiding').eq(i),
|
||||
outfit_el = $.tmpl('outfitTemplate', outfit);
|
||||
outfit_el = $.tmpl('outfitTemplate', outfit.clone());
|
||||
if(next_child.length) {
|
||||
outfit_el.insertBefore(next_child);
|
||||
} else {
|
||||
outfit_el.appendTo(outfits_list_el);
|
||||
}
|
||||
updateActiveOutfit();
|
||||
outfit_el.hide().show('normal');
|
||||
|
||||
var naturalWidth = outfit_el.css('width');
|
||||
log("Natural width is", naturalWidth, outfit_el.width());
|
||||
outfit_el.width(0).animate({width: naturalWidth}, 'normal');
|
||||
listSubscribeToImage(outfit);
|
||||
});
|
||||
|
||||
wardrobe.user.bind('removeOutfit', function (outfit, i) {
|
||||
wardrobe.outfits.bind('removeOutfit', function (outfit, i) {
|
||||
var outfit_el = outfits_list_el.children().not('.hiding').eq(i);
|
||||
outfit_el.addClass('hiding').stop(true).hide('normal', function () { outfit_el.remove() });
|
||||
outfit_el.addClass('hiding').stop(true).animate({width: 0}, 'normal', function () { outfit_el.remove() });
|
||||
listUnsubscribeFromImage(outfit);
|
||||
});
|
||||
|
||||
$('#preview-outfits h4').live('click', function () {
|
||||
wardrobe.outfit.load($(this).tmplItem().data.id);
|
||||
$('#preview-outfits li header, #preview-outfits li .outfit-thumbnail-wrapper').live('click', function () {
|
||||
wardrobe.outfits.load($(this).tmplItem().data.id);
|
||||
});
|
||||
|
||||
$('a.outfit-rename-button').live('click', function (e) {
|
||||
e.preventDefault();
|
||||
var li = $(this).closest('li').addClass('renaming'),
|
||||
name = li.find('h4').text();
|
||||
name = li.find('span.outfit-name').text();
|
||||
li.find('input.outfit-rename-field').val(name).focus();
|
||||
});
|
||||
|
||||
|
@ -550,7 +586,7 @@ View.Outfits = function (wardrobe) {
|
|||
li = el.closest('li').removeClass('renaming');
|
||||
if(new_name != outfit.name) {
|
||||
li.startLoading();
|
||||
wardrobe.user.renameOutfit(outfit, new_name);
|
||||
wardrobe.outfits.renameOutfit(outfit, new_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -568,7 +604,8 @@ View.Outfits = function (wardrobe) {
|
|||
this.blur();
|
||||
});
|
||||
|
||||
$('button.outfit-delete').live('click', function (e) {
|
||||
$('a.outfit-delete').live('click', function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$(this).closest('li').addClass('confirming-deletion');
|
||||
});
|
||||
|
@ -576,9 +613,9 @@ View.Outfits = function (wardrobe) {
|
|||
$('a.outfit-delete-confirmation-yes').live('click', function (e) {
|
||||
var outfit = $(this).tmplItem().data;
|
||||
e.preventDefault();
|
||||
wardrobe.user.destroyOutfit(outfit);
|
||||
if(wardrobe.outfit.getOutfit().id == outfit.id) {
|
||||
wardrobe.outfit.setId(null);
|
||||
wardrobe.outfits.destroyOutfit(outfit);
|
||||
if(wardrobe.outfits.getOutfit().id == outfit.id) {
|
||||
wardrobe.outfits.setId(null);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -587,16 +624,25 @@ View.Outfits = function (wardrobe) {
|
|||
$(this).closest('li').removeClass('confirming-deletion');
|
||||
});
|
||||
|
||||
stars.live('click', function () {
|
||||
stars.live('click', function (e) {
|
||||
e.stopPropagation();
|
||||
var el = $(this);
|
||||
el.closest('li').startLoading();
|
||||
wardrobe.user.toggleOutfitStar(el.tmplItem().data);
|
||||
wardrobe.outfits.toggleOutfitStar(el.tmplItem().data);
|
||||
});
|
||||
|
||||
function pathToUrl(path) {
|
||||
var host = document.location.protocol + "//" + document.location.host;
|
||||
if(document.location.port) host += ":" + document.location.port;
|
||||
return host + path;
|
||||
}
|
||||
|
||||
function generateOutfitPermalink(outfit) {
|
||||
return pathToUrl("/outfits/" + outfit.id);
|
||||
}
|
||||
|
||||
function setOutfitPermalink(outfit, outfit_permalink_el, outfit_url_el) {
|
||||
var url = document.location.protocol + "//" + document.location.host;
|
||||
if(document.location.port) url += ":" + document.location.port;
|
||||
url += "/outfits/" + outfit.id;
|
||||
var url = generateOutfitPermalink(outfit);
|
||||
outfit_permalink_el.attr('href', url);
|
||||
if(outfit_url_el) outfit_url_el.val(url);
|
||||
}
|
||||
|
@ -605,10 +651,6 @@ View.Outfits = function (wardrobe) {
|
|||
setOutfitPermalink(outfit, current_outfit_permalink_el);
|
||||
}
|
||||
|
||||
function setSharedOutfitPermalink(outfit) {
|
||||
setOutfitPermalink(outfit, shared_outfit_permalink_el, shared_outfit_url_el);
|
||||
}
|
||||
|
||||
function setActiveOutfit(outfit) {
|
||||
outfits_list_el.find('li.active').removeClass('active');
|
||||
if(outfit.id) {
|
||||
|
@ -620,33 +662,210 @@ View.Outfits = function (wardrobe) {
|
|||
}
|
||||
|
||||
function updateActiveOutfit() {
|
||||
setActiveOutfit(wardrobe.outfit.getOutfit());
|
||||
setActiveOutfit(wardrobe.outfits.getOutfit());
|
||||
}
|
||||
|
||||
wardrobe.outfit.bind('setOutfit', setActiveOutfit);
|
||||
wardrobe.outfit.bind('outfitNotFound', setActiveOutfit);
|
||||
wardrobe.outfits.bind('setOutfit', setActiveOutfit);
|
||||
wardrobe.outfits.bind('outfitNotFound', setActiveOutfit);
|
||||
|
||||
wardrobe.user.bind('outfitRenamed', function (outfit) {
|
||||
if(outfit.id == wardrobe.outfit.getId()) {
|
||||
wardrobe.outfits.bind('outfitRenamed', function (outfit) {
|
||||
if(outfit.id == wardrobe.outfits.getId()) {
|
||||
save_current_outfit_name_el.text(outfit.name);
|
||||
}
|
||||
});
|
||||
|
||||
function outfitElement(outfit) {
|
||||
return outfits_el.find('li.outfit-' + outfit.id);
|
||||
}
|
||||
|
||||
wardrobe.outfits.bind('saveSuccess', function (outfit) {
|
||||
listSubscribeToImage(outfit);
|
||||
});
|
||||
|
||||
wardrobe.image_subscriptions.bind('imageEnqueued', function (outfit) {
|
||||
if(outfit.id in list_image_subscriptions) {
|
||||
log("List sees imageEnqueued for", outfit);
|
||||
outfitElement(outfit).removeClass('thumbnail-loaded');
|
||||
}
|
||||
});
|
||||
|
||||
wardrobe.image_subscriptions.bind('imageReady', function (outfit) {
|
||||
if(outfit.id in list_image_subscriptions) {
|
||||
log("List sees imageReady for", outfit);
|
||||
listUnsubscribeFromImage(outfit);
|
||||
|
||||
var src = outfit.image_versions.small + '?' + (new Date()).getTime();
|
||||
outfitElement(outfit).addClass('thumbnail-loaded').addClass('thumbnail-available').
|
||||
find('img.outfit-thumbnail').attr('src', src);
|
||||
}
|
||||
});
|
||||
|
||||
/* Sharing */
|
||||
|
||||
var sharing = new function Sharing() {
|
||||
var WRAPPER = $('#preview-sharing');
|
||||
var sharing_url_els = {
|
||||
permalink: $('#preview-sharing-permalink-url'),
|
||||
large_image: $('#preview-sharing-large-image-url'),
|
||||
medium_image: $('#preview-sharing-medium-image-url'),
|
||||
small_image: $('#preview-sharing-small-image-url'),
|
||||
};
|
||||
var format_selector_els = $('#preview-sharing-url-formats li');
|
||||
var thumbnail_el = $('#preview-sharing-thumbnail');
|
||||
|
||||
var formats = {
|
||||
plain: {
|
||||
image: function (url) { return url },
|
||||
text: function (url) { return url }
|
||||
},
|
||||
html: {
|
||||
image: function (url, permalink) {
|
||||
return '<a href="' + permalink + '"><img src="' + url + '" /></a>';
|
||||
},
|
||||
text: function (url) {
|
||||
return '<a href="' + url + '">Dress to Impress</a>';
|
||||
}
|
||||
},
|
||||
bbcode: {
|
||||
image: function (url, permalink) {
|
||||
return '[URL=' + permalink + '][IMG]' + url + '[/IMG][/URL]';
|
||||
},
|
||||
text: function (url) {
|
||||
return '[URL=' + url + ']Dress to Impress[/URL]';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var format = formats.plain;
|
||||
var urls = {permalink: null, small_image: null, medium_image: null,
|
||||
large_image: null};
|
||||
|
||||
format_selector_els.click(function () {
|
||||
var selector_el = $(this);
|
||||
format_selector_els.removeClass('active');
|
||||
selector_el.addClass('active');
|
||||
log("Setting sharing URL format:", selector_el.attr('data-format'));
|
||||
format = formats[selector_el.attr('data-format')];
|
||||
formatUrls();
|
||||
});
|
||||
|
||||
var image_subscription = null;
|
||||
function unsubscribeFromImage() {
|
||||
wardrobe.image_subscriptions.unsubscribe(image_subscription);
|
||||
image_subscription = null;
|
||||
}
|
||||
|
||||
function subscribeToImage(outfit) {
|
||||
image_subscription = wardrobe.image_subscriptions.subscribe(outfit);
|
||||
}
|
||||
|
||||
function subscribeToImageIfVisible(outfit) {
|
||||
if(outfit && sidebar_el.hasClass('sharing')) {
|
||||
subscribeToImage(outfit);
|
||||
}
|
||||
}
|
||||
|
||||
var current_shared_outfit = {id: null};
|
||||
this.setOutfit = function (outfit) {
|
||||
// If outfit has no ID but we're already on the Sharing tab (e.g. user is
|
||||
// on Sharing but goes back in history to a no-ID outfit), we can't
|
||||
// exactly do anything with it but submit it for sharing.
|
||||
if(!outfit.id) {
|
||||
sharing.startLoading();
|
||||
wardrobe.outfits.share(outfit);
|
||||
return false;
|
||||
}
|
||||
|
||||
// But if the outfit does have a valid ID, we're good to go. If it's the
|
||||
// same as the currently shared outfit ID, then don't even change
|
||||
// anything. If it's new, then change everything.
|
||||
if(outfit.id != current_shared_outfit.id) {
|
||||
// The current shared outfit needs to be a clone, or else modifications
|
||||
// to the active outfit will show up here, too, and then our comparison
|
||||
// to discover if this is a new outfit ID or not fails.
|
||||
current_shared_outfit = outfit.clone();
|
||||
urls.permalink = generateOutfitPermalink(outfit);
|
||||
urls.small_image = pathToUrl(outfit.image_versions.small);
|
||||
urls.medium_image = pathToUrl(outfit.image_versions.medium);
|
||||
urls.large_image = pathToUrl(outfit.image_versions.large);
|
||||
formatUrls();
|
||||
WRAPPER.removeClass('thumbnail-available');
|
||||
subscribeToImageIfVisible(current_shared_outfit);
|
||||
}
|
||||
WRAPPER.addClass('urls-loaded');
|
||||
}
|
||||
|
||||
this.startLoading = function () {
|
||||
WRAPPER.removeClass('urls-loaded');
|
||||
}
|
||||
|
||||
this.onHide = function () {
|
||||
unsubscribeFromImage();
|
||||
}
|
||||
|
||||
this.onShow = function () {
|
||||
subscribeToImageIfVisible(wardrobe.outfits.getOutfit());
|
||||
}
|
||||
|
||||
function formatUrls() {
|
||||
formatImageUrl('small_image');
|
||||
formatImageUrl('medium_image');
|
||||
formatImageUrl('large_image');
|
||||
formatTextUrl('permalink');
|
||||
}
|
||||
|
||||
function formatTextUrl(key) {
|
||||
formatUrl(key, format.text(urls[key]));
|
||||
}
|
||||
|
||||
function formatImageUrl(key) {
|
||||
formatUrl(key, format.image(urls[key], urls.permalink));
|
||||
}
|
||||
|
||||
function formatUrl(key, url) {
|
||||
sharing_url_els[key].val(url);
|
||||
}
|
||||
|
||||
wardrobe.image_subscriptions.bind('imageEnqueued', function (outfit) {
|
||||
if(outfit.id == current_shared_outfit.id) {
|
||||
log("Sharing thumbnail enqueued for outfit", outfit);
|
||||
WRAPPER.removeClass('thumbnail-loaded');
|
||||
}
|
||||
});
|
||||
|
||||
wardrobe.image_subscriptions.bind('imageReady', function (outfit) {
|
||||
if(outfit.id == current_shared_outfit.id) {
|
||||
log("Sharing thumbnail ready for outfit", outfit);
|
||||
var src = outfit.image_versions.small + '?' + outfit.image_layers_hash;
|
||||
thumbnail_el.attr('src', src);
|
||||
WRAPPER.addClass('thumbnail-loaded');
|
||||
WRAPPER.addClass('thumbnail-available');
|
||||
unsubscribeFromImage(outfit);
|
||||
}
|
||||
});
|
||||
|
||||
wardrobe.outfits.bind('updateSuccess', function (outfit) {
|
||||
if(sidebar_el.hasClass('sharing')) {
|
||||
subscribeToImage(outfit);
|
||||
}
|
||||
});
|
||||
|
||||
wardrobe.outfits.bind('setOutfit', function (outfit) {
|
||||
log("Sharing sees the setOutfit signal, and will set", outfit);
|
||||
sharing.setOutfit(outfit);
|
||||
});
|
||||
}
|
||||
|
||||
/* Saving */
|
||||
|
||||
save_current_outfit_el.click(function () {
|
||||
wardrobe.outfit.update();
|
||||
wardrobe.outfits.update();
|
||||
});
|
||||
|
||||
new_outfit_form_el.submit(function (e) {
|
||||
e.preventDefault();
|
||||
new_outfit_form_el.startLoading();
|
||||
wardrobe.outfit.create({starred: new_outfit_form_el.hasClass('starred'), name: new_outfit_name_el.val()});
|
||||
});
|
||||
|
||||
$('#share-outfit').click(function () {
|
||||
save_outfit_wrapper_el.startLoading();
|
||||
wardrobe.outfit.share();
|
||||
wardrobe.outfits.create({starred: new_outfit_form_el.hasClass('starred'), name: new_outfit_name_el.val()});
|
||||
});
|
||||
|
||||
new_outfit_form_el.find('div.outfit-star').click(function () {
|
||||
|
@ -664,32 +883,31 @@ View.Outfits = function (wardrobe) {
|
|||
save_error_el.text(text).notify();
|
||||
}
|
||||
|
||||
wardrobe.outfit.bind('saveSuccess', function (outfit) {
|
||||
wardrobe.outfits.bind('saveSuccess', function (outfit) {
|
||||
save_success_el.notify();
|
||||
});
|
||||
|
||||
wardrobe.outfit.bind('createSuccess', function (outfit) {
|
||||
wardrobe.user.addOutfit(outfit);
|
||||
wardrobe.outfits.bind('createSuccess', function (outfit) {
|
||||
showOutfits();
|
||||
hideNewOutfitForm();
|
||||
});
|
||||
|
||||
wardrobe.outfit.bind('updateSuccess', function (outfit) {
|
||||
wardrobe.user.updateOutfit(outfit);
|
||||
});
|
||||
|
||||
wardrobe.outfit.bind('shareSuccess', function (outfit) {
|
||||
function shareComplete(outfit) {
|
||||
save_outfit_wrapper_el.stopLoading().addClass('shared-outfit');
|
||||
setSharedOutfitPermalink(outfit);
|
||||
});
|
||||
sharing.setOutfit(outfit);
|
||||
showSharing();
|
||||
}
|
||||
|
||||
wardrobe.outfits.bind('shareSuccess', shareComplete);
|
||||
wardrobe.outfits.bind('shareSkipped', shareComplete);
|
||||
|
||||
function clearSharedOutfit() {
|
||||
save_outfit_wrapper_el.removeClass('shared-outfit');
|
||||
}
|
||||
|
||||
wardrobe.outfit.bind('updateClosetItems', clearSharedOutfit);
|
||||
wardrobe.outfit.bind('updateWornItems', clearSharedOutfit);
|
||||
wardrobe.outfit.bind('updatePetState', clearSharedOutfit);
|
||||
wardrobe.outfits.bind('updateClosetItems', clearSharedOutfit);
|
||||
wardrobe.outfits.bind('updateWornItems', clearSharedOutfit);
|
||||
wardrobe.outfits.bind('updatePetState', clearSharedOutfit);
|
||||
|
||||
function saveFailure(outfit, response) {
|
||||
var errors = response.errors;
|
||||
|
@ -712,16 +930,16 @@ View.Outfits = function (wardrobe) {
|
|||
liForOutfit(outfit).stopLoading();
|
||||
}
|
||||
|
||||
wardrobe.outfit.bind('saveFailure', saveFailure);
|
||||
wardrobe.user.bind('saveFailure', saveFailure)
|
||||
wardrobe.outfit.bind('shareFailure', function (outfit, response) {
|
||||
wardrobe.outfits.bind('saveFailure', saveFailure);
|
||||
wardrobe.outfits.bind('saveFailure', saveFailure)
|
||||
wardrobe.outfits.bind('shareFailure', function (outfit, response) {
|
||||
save_outfit_wrapper_el.stopLoading();
|
||||
saveFailure(outfit, response);
|
||||
});
|
||||
|
||||
/* Error */
|
||||
|
||||
wardrobe.outfit.bind('outfitNotFound', function () {
|
||||
wardrobe.outfits.bind('outfitNotFound', function () {
|
||||
outfit_not_found_el.notify();
|
||||
});
|
||||
}
|
||||
|
@ -733,7 +951,7 @@ View.PetStateForm = function (wardrobe) {
|
|||
button_query = form_query + ' button';
|
||||
$(button_query).live('click', function (e) {
|
||||
e.preventDefault();
|
||||
wardrobe.outfit.setPetStateById(+$(this).data('value'));
|
||||
wardrobe.outfits.setPetStateById(+$(this).data('value'));
|
||||
});
|
||||
|
||||
function updatePetState(pet_state) {
|
||||
|
@ -743,7 +961,7 @@ View.PetStateForm = function (wardrobe) {
|
|||
}
|
||||
}
|
||||
|
||||
wardrobe.outfit.bind('petTypeLoaded', function (pet_type) {
|
||||
wardrobe.outfits.bind('petTypeLoaded', function (pet_type) {
|
||||
var ids = pet_type.pet_state_ids, i, id, li, button, label;
|
||||
ul.children().remove();
|
||||
if(ids.length == 1) {
|
||||
|
@ -761,18 +979,18 @@ View.PetStateForm = function (wardrobe) {
|
|||
button.appendTo(li);
|
||||
li.appendTo(ul);
|
||||
}
|
||||
updatePetState(wardrobe.outfit.getPetState());
|
||||
updatePetState(wardrobe.outfits.getPetState());
|
||||
}
|
||||
});
|
||||
|
||||
wardrobe.outfit.bind('updatePetState', updatePetState);
|
||||
wardrobe.outfits.bind('updatePetState', updatePetState);
|
||||
}
|
||||
|
||||
View.PetTypeForm = function (wardrobe) {
|
||||
var form = $('#pet-type-form'), dropdowns = {}, loaded = false;
|
||||
form.submit(function (e) {
|
||||
e.preventDefault();
|
||||
wardrobe.outfit.setPetTypeByColorAndSpecies(
|
||||
wardrobe.outfits.setPetTypeByColorAndSpecies(
|
||||
+dropdowns.color.val(), +dropdowns.species.val()
|
||||
);
|
||||
}).children('select').each(function () {
|
||||
|
@ -803,12 +1021,12 @@ View.PetTypeForm = function (wardrobe) {
|
|||
});
|
||||
});
|
||||
loaded = true;
|
||||
updatePetType(wardrobe.outfit.getPetType());
|
||||
updatePetType(wardrobe.outfits.getPetType());
|
||||
});
|
||||
|
||||
wardrobe.outfit.bind('updatePetType', updatePetType);
|
||||
wardrobe.outfits.bind('updatePetType', updatePetType);
|
||||
|
||||
wardrobe.outfit.bind('petTypeNotFound', function () {
|
||||
wardrobe.outfits.bind('petTypeNotFound', function () {
|
||||
$('#pet-type-not-found').show('normal').delay(3000).hide('fast');
|
||||
});
|
||||
}
|
||||
|
@ -865,7 +1083,7 @@ View.ReportBrokenImage = function (wardrobe) {
|
|||
var baseURL = link.attr('data-base-url');
|
||||
|
||||
function updateLink() {
|
||||
var assets = wardrobe.outfit.getVisibleAssets();
|
||||
var assets = wardrobe.outfits.getVisibleAssets();
|
||||
var url = baseURL + "?";
|
||||
|
||||
for(var i = 0; i < assets.length; i++) {
|
||||
|
@ -876,9 +1094,9 @@ View.ReportBrokenImage = function (wardrobe) {
|
|||
link.attr('href', url);
|
||||
}
|
||||
|
||||
wardrobe.outfit.bind('updateWornItems', updateLink);
|
||||
wardrobe.outfit.bind('updateItemAssets', updateLink);
|
||||
wardrobe.outfit.bind('updatePetState', updateLink);
|
||||
wardrobe.outfits.bind('updateWornItems', updateLink);
|
||||
wardrobe.outfits.bind('updateItemAssets', updateLink);
|
||||
wardrobe.outfits.bind('updatePetState', updateLink);
|
||||
}
|
||||
|
||||
View.Search = function (wardrobe) {
|
||||
|
|
|
@ -7,4 +7,4 @@ var main_wardrobe = new Wardrobe(), View = Wardrobe.getStandardView({
|
|||
});
|
||||
main_wardrobe.registerViews(View);
|
||||
main_wardrobe.initialize();
|
||||
main_wardrobe.outfit.loadData(INITIAL_OUTFIT_DATA);
|
||||
main_wardrobe.outfits.loadData(INITIAL_OUTFIT_DATA);
|
||||
|
|
|
@ -7,9 +7,6 @@ function arraysMatch(array1, array2) {
|
|||
return array1 == array2;
|
||||
}
|
||||
temp = [];
|
||||
if ( (!array1[0]) || (!array2[0]) ) {
|
||||
return false;
|
||||
}
|
||||
if (array1.length != array2.length) {
|
||||
return false;
|
||||
}
|
||||
|
@ -28,7 +25,7 @@ function arraysMatch(array1, array2) {
|
|||
return true;
|
||||
}
|
||||
|
||||
Array.prototype.map = function (property) {
|
||||
Array.prototype.mapProperty = function (property) {
|
||||
return $.map(this, function (element) {
|
||||
return element[property];
|
||||
});
|
||||
|
@ -79,8 +76,19 @@ function Wardrobe() {
|
|||
function Asset(newData) {
|
||||
var asset = this;
|
||||
|
||||
function size_key(size) {
|
||||
return size[0] + 'x' + size[1];
|
||||
}
|
||||
|
||||
this.image_urls_by_size_key = {};
|
||||
var image;
|
||||
for(var i = 0; i < newData.images.length; i++) {
|
||||
image = newData.images[i];
|
||||
this.image_urls_by_size_key[size_key(image.size)] = image.url;
|
||||
}
|
||||
|
||||
this.imageURL = function (size) {
|
||||
return Wardrobe.IMAGE_CONFIG.base_url + this.s3_path + "/" + size[0] + "x" + size[1] + ".png";
|
||||
return this.image_urls_by_size_key[size_key(size)];
|
||||
}
|
||||
|
||||
this.update = function (data) {
|
||||
|
@ -268,17 +276,24 @@ function Wardrobe() {
|
|||
closet_item_ids = new_ids.unworn.concat(new_ids.worn);
|
||||
}
|
||||
|
||||
if(typeof data != 'undefined') {
|
||||
this.color_id = data.color_id;
|
||||
this.id = data.id;
|
||||
this.name = data.name;
|
||||
this.pet_state_id = data.pet_state_id;
|
||||
this.starred = data.starred;
|
||||
this.species_id = data.species_id;
|
||||
this.setWornAndUnwornItemIds(data.worn_and_unworn_item_ids);
|
||||
function loadAttributes(data) {
|
||||
outfit.color_id = data.color_id;
|
||||
outfit.id = data.id;
|
||||
outfit.name = data.name;
|
||||
outfit.pet_state_id = data.pet_state_id;
|
||||
outfit.starred = data.starred;
|
||||
outfit.species_id = data.species_id;
|
||||
outfit.image_versions = data.image_versions;
|
||||
outfit.image_enqueued = data.image_enqueued;
|
||||
outfit.image_layers_hash = data.image_layers_hash;
|
||||
outfit.setWornAndUnwornItemIds(data.worn_and_unworn_item_ids);
|
||||
new_record = false;
|
||||
}
|
||||
|
||||
if(typeof data != 'undefined') {
|
||||
loadAttributes(data);
|
||||
}
|
||||
|
||||
this.closet_items = [];
|
||||
this.worn_items = [];
|
||||
|
||||
|
@ -327,11 +342,11 @@ function Wardrobe() {
|
|||
new_items = [], new_worn_item_ids = [];
|
||||
if(added_item) {
|
||||
// now that we've loaded, check for conflicts on the added item
|
||||
item_zones = added_item.getAssetsFitting(outfit.pet_type).map('zone_id');
|
||||
item_zones = added_item.getAssetsFitting(outfit.pet_type).mapProperty('zone_id');
|
||||
item_zones_length = item_zones.length;
|
||||
for(var i = 0; i < outfit.worn_items.length; i++) {
|
||||
existing_item = outfit.worn_items[i];
|
||||
existing_item_zones = existing_item.getAssetsFitting(outfit.pet_type).map('zone_id');
|
||||
existing_item_zones = existing_item.getAssetsFitting(outfit.pet_type).mapProperty('zone_id');
|
||||
passed = true;
|
||||
if(existing_item != added_item) {
|
||||
for(var j = 0; j < item_zones_length; j++) {
|
||||
|
@ -369,23 +384,6 @@ function Wardrobe() {
|
|||
}
|
||||
}
|
||||
|
||||
function sendUpdate(outfit_data, success, failure) {
|
||||
$.ajax({
|
||||
url: '/outfits/' + outfit.id,
|
||||
type: 'post',
|
||||
data: {'_method': 'put', outfit: outfit_data},
|
||||
success: function () {
|
||||
Outfit.cache[outfit.id] = outfit;
|
||||
success(outfit);
|
||||
},
|
||||
error: function (xhr) {
|
||||
if(typeof failure !== 'undefined') {
|
||||
failure(outfit, $.parseJSON(xhr.responseText));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.closetItem = function (item, updateClosetItemsCallback) {
|
||||
if(!hasItemInCloset(item)) {
|
||||
this.closet_items.push(item);
|
||||
|
@ -416,6 +414,14 @@ function Wardrobe() {
|
|||
return visible_assets;
|
||||
}
|
||||
|
||||
this.isIdenticalTo = function (other) {
|
||||
return other && // other exists
|
||||
this.constructor == other.constructor && // other is an outfit
|
||||
this.getPetStateId() == other.getPetStateId() &&
|
||||
arraysMatch(this.getWornItemIds(), other.getWornItemIds()) &&
|
||||
arraysMatch(this.getClosetItemIds(), other.getClosetItemIds());
|
||||
}
|
||||
|
||||
this.rename = function (new_name, success, failure) {
|
||||
this.updateAttributes({name: new_name}, success, failure);
|
||||
}
|
||||
|
@ -522,6 +528,9 @@ function Wardrobe() {
|
|||
new_outfit.id = outfit.id;
|
||||
new_outfit.name = outfit.name;
|
||||
new_outfit.starred = outfit.starred;
|
||||
new_outfit.image_enqueued = outfit.image_enqueued;
|
||||
new_outfit.image_versions = outfit.image_versions;
|
||||
new_outfit.image_layers_hash = outfit.image_layers_hash;
|
||||
return new_outfit;
|
||||
}
|
||||
|
||||
|
@ -540,6 +549,13 @@ function Wardrobe() {
|
|||
outfit.setWornAndUnwornItemIds(new_ids);
|
||||
}
|
||||
|
||||
function updateFromSaveResponse(data) {
|
||||
outfit.id = data.id;
|
||||
outfit.image_versions = data.image_versions;
|
||||
outfit.image_enqueued = data.image_enqueued;
|
||||
outfit.image_layers_hash = data.image_layers_hash;
|
||||
}
|
||||
|
||||
this.destroy = function (success) {
|
||||
$.ajax({
|
||||
url: '/outfits/' + outfit.id + '.json',
|
||||
|
@ -554,10 +570,11 @@ function Wardrobe() {
|
|||
url: '/outfits',
|
||||
type: 'post',
|
||||
data: {outfit: getAttributes()},
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
new_record = false;
|
||||
outfit.id = data;
|
||||
Outfit.cache[data] = outfit;
|
||||
updateFromSaveResponse(data);
|
||||
Outfit.cache[outfit.id] = outfit;
|
||||
success(outfit);
|
||||
},
|
||||
error: function (xhr) {
|
||||
|
@ -566,6 +583,32 @@ function Wardrobe() {
|
|||
});
|
||||
}
|
||||
|
||||
this.reload = function (success) {
|
||||
Outfit.load(this.id, function (new_outfit) {
|
||||
loadAttributes(new_outfit);
|
||||
success(outfit);
|
||||
});
|
||||
}
|
||||
|
||||
function sendUpdate(outfit_data, success, failure) {
|
||||
$.ajax({
|
||||
url: '/outfits/' + outfit.id,
|
||||
type: 'post',
|
||||
data: {'_method': 'put', outfit: outfit_data},
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
updateFromSaveResponse(data);
|
||||
Outfit.cache[outfit.id] = outfit;
|
||||
success(outfit);
|
||||
},
|
||||
error: function (xhr) {
|
||||
if(typeof failure !== 'undefined') {
|
||||
failure(outfit, $.parseJSON(xhr.responseText));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.updateAttributes = function (attributes, success, failure) {
|
||||
var outfit_data = {};
|
||||
for(var key in attributes) {
|
||||
|
@ -583,6 +626,11 @@ function Wardrobe() {
|
|||
if(typeof Outfit.cache[id] !== 'undefined') {
|
||||
callback(Outfit.cache[id]);
|
||||
} else {
|
||||
Outfit.load(id, callback);
|
||||
}
|
||||
}
|
||||
|
||||
Outfit.load = function (id, callback) {
|
||||
$.ajax({
|
||||
url: '/outfits/' + id + '.json',
|
||||
success: function (data) {
|
||||
|
@ -595,7 +643,6 @@ function Wardrobe() {
|
|||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Outfit.loadForCurrentUser = function (success) {
|
||||
var outfits = [];
|
||||
|
@ -788,8 +835,13 @@ function Wardrobe() {
|
|||
|
||||
Controller.all = {};
|
||||
|
||||
Controller.all.Outfit = function OutfitController() {
|
||||
var controller = this, outfit = new Outfit;
|
||||
Controller.all.Outfits = function OutfitsController() {
|
||||
// TODO: clean up the merge of outfits and user controller. Some is already
|
||||
// done, but I'm sure there's tons of redundant code still lying around.
|
||||
|
||||
/* Current outfit management */
|
||||
|
||||
var controller = this, outfit = new Outfit, last_shared_outfit = null;
|
||||
|
||||
this.in_transaction = false;
|
||||
|
||||
|
@ -862,6 +914,7 @@ function Wardrobe() {
|
|||
}
|
||||
outfit.create(
|
||||
function (outfit) {
|
||||
insertOutfit(outfit);
|
||||
controller.events.trigger('saveSuccess', outfit);
|
||||
controller.events.trigger('createSuccess', outfit);
|
||||
controller.events.trigger('setOutfit', outfit);
|
||||
|
@ -920,13 +973,25 @@ function Wardrobe() {
|
|||
}
|
||||
|
||||
this.share = function () {
|
||||
var sharedOutfit = outfit.clone();
|
||||
sharedOutfit.anonymous = true;
|
||||
sharedOutfit.create(
|
||||
if(outfit.id) {
|
||||
// If this is a user-saved outfit (user is logged in), no need to
|
||||
// re-share it. Skip to using the current outfit.
|
||||
controller.events.trigger('shareSkipped', outfit);
|
||||
} else if(outfit.isIdenticalTo(last_shared_outfit)) {
|
||||
// If the outfit hasn't changed since last time we shared it, no need to
|
||||
// re-share it. Skip to using the last shared outfit.
|
||||
controller.events.trigger('shareSkipped', last_shared_outfit);
|
||||
} else {
|
||||
// Otherwise, this is a fresh outfit that needs to be shared. Try, and
|
||||
// report success or failure.
|
||||
last_shared_outfit = outfit.clone();
|
||||
last_shared_outfit.anonymous = true;
|
||||
last_shared_outfit.create(
|
||||
controller.event('shareSuccess'),
|
||||
controller.event('shareFailure')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.unclosetItem = function (item) {
|
||||
outfit.unclosetItem(
|
||||
|
@ -943,6 +1008,7 @@ function Wardrobe() {
|
|||
this.update = function () {
|
||||
outfit.update(
|
||||
function (outfit) {
|
||||
updateUserOutfit(outfit);
|
||||
controller.events.trigger('saveSuccess', outfit),
|
||||
controller.events.trigger('updateSuccess', outfit)
|
||||
},
|
||||
|
@ -958,6 +1024,159 @@ function Wardrobe() {
|
|||
controller.event('updateItemAssets')
|
||||
);
|
||||
}
|
||||
|
||||
/* User outfits management */
|
||||
|
||||
var outfits = [], outfits_loaded = false;
|
||||
|
||||
function compareOutfits(a, b) {
|
||||
if(a.starred) {
|
||||
if(!b.starred) return -1;
|
||||
} else if(b.starred) {
|
||||
return 1;
|
||||
}
|
||||
if(a.name < b.name) return -1;
|
||||
else if(a.name == b.name) return 0;
|
||||
else return 1;
|
||||
}
|
||||
|
||||
function insertOutfit(outfit) {
|
||||
for(var i = 0; i < outfits.length; i++) {
|
||||
if(compareOutfits(outfit, outfits[i]) < 0) {
|
||||
outfits.splice(i, 0, outfit);
|
||||
controller.events.trigger('addOutfit', outfit, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
controller.events.trigger('addOutfit', outfit, outfits.length);
|
||||
outfits.push(outfit);
|
||||
}
|
||||
|
||||
function sortOutfits(outfits) {
|
||||
outfits.sort(compareOutfits);
|
||||
}
|
||||
|
||||
function yankOutfit(outfit) {
|
||||
var i;
|
||||
for(i = 0; i < outfits.length; i++) {
|
||||
if(outfit.id == outfits[i].id) {
|
||||
outfits.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
controller.events.trigger('removeOutfit', outfit, i);
|
||||
}
|
||||
|
||||
this.destroyOutfit = function (outfit) {
|
||||
outfit.destroy(function () {
|
||||
yankOutfit(outfit);
|
||||
});
|
||||
}
|
||||
|
||||
this.loadOutfits = function () {
|
||||
if(!outfits_loaded) {
|
||||
Outfit.loadForCurrentUser(function (new_outfits) {
|
||||
outfits = new_outfits;
|
||||
outfits_loaded = true;
|
||||
sortOutfits(outfits);
|
||||
controller.events.trigger('outfitsLoaded', outfits);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.renameOutfit = function (outfit, new_name) {
|
||||
var old_name = outfit.name;
|
||||
outfit.rename(new_name, function () {
|
||||
yankOutfit(outfit);
|
||||
insertOutfit(outfit);
|
||||
controller.events.trigger('outfitRenamed', outfit);
|
||||
}, function (outfit_copy, response) {
|
||||
outfit.name = old_name;
|
||||
controller.events.trigger('saveFailure', outfit_copy, response);
|
||||
});
|
||||
}
|
||||
|
||||
this.toggleOutfitStar = function (outfit) {
|
||||
outfit.toggleStar(function () {
|
||||
yankOutfit(outfit);
|
||||
insertOutfit(outfit);
|
||||
controller.events.trigger('outfitStarToggled', outfit);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUserOutfit(outfit) {
|
||||
for(var i = 0; i < outfits.length; i++) {
|
||||
if(outfits[i].id == outfit.id) {
|
||||
outfits[i] = outfit.clone();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controller.all.ImageSubscriptions = function ImagesSubscriptionsController() {
|
||||
var outfitSubscriptionTotals = {};
|
||||
var DELAY = 5000;
|
||||
var controller = this;
|
||||
|
||||
function checkSubscription(outfit_id) {
|
||||
Outfit.find(outfit_id, function (outfit) {
|
||||
log("Checking image for", outfit);
|
||||
outfit.reload(function () {
|
||||
if(outfitSubscriptionTotals[outfit_id] > 0) {
|
||||
if(outfit.image_enqueued) {
|
||||
log("Outfit image still enqueued; will try again soon", outfit);
|
||||
setTimeout(function () { checkSubscription(outfit_id) }, DELAY);
|
||||
} else {
|
||||
// Unsubscribe everyone from this outfit and fire ready events
|
||||
delete outfitSubscriptionTotals[outfit_id];
|
||||
controller.events.trigger('imageReady', outfit);
|
||||
}
|
||||
} else {
|
||||
log("Outfit was unsubscribed", outfit);
|
||||
delete outfitSubscriptionTotals[outfit_id];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.subscribe = function (outfit) {
|
||||
if(outfit.image_enqueued) {
|
||||
if(outfit.id in outfitSubscriptionTotals) {
|
||||
// The subscription is already running. Just mark that one more
|
||||
// consumer is interested in it, and they'll all get a response soon.
|
||||
outfitSubscriptionTotals[outfit.id] += 1;
|
||||
} else {
|
||||
// This is a new subscription! Let's start checking it.
|
||||
outfitSubscriptionTotals[outfit.id] = 1;
|
||||
checkSubscription(outfit.id);
|
||||
}
|
||||
|
||||
// Regardless, trigger the enqueued event for the new consumer's sake.
|
||||
controller.events.trigger('imageEnqueued', outfit);
|
||||
} else {
|
||||
// Otherwise, never bother checking: skip straight to the ready phase.
|
||||
// Give it an instant timeout so that we're sure the consumer is ready
|
||||
// for the event. (It can be tricky when the consumer assigns this
|
||||
// return value somewhere to know if it cares about the event, so the
|
||||
// event can't fire before the return.)
|
||||
setTimeout(function () {
|
||||
controller.events.trigger('imageReady', outfit)
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return outfit;
|
||||
}
|
||||
|
||||
this.unsubscribe = function (outfit) {
|
||||
if(outfit && outfit.id in outfitSubscriptionTotals) {
|
||||
if(outfitSubscriptionTotals[outfit.id] > 1) {
|
||||
outfitSubscriptionTotals[outfit.id] -= 1;
|
||||
} else {
|
||||
delete outfitSubscriptionTotals[outfit.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controller.all.BasePet = function BasePetController() {
|
||||
|
@ -1028,96 +1247,6 @@ function Wardrobe() {
|
|||
}
|
||||
}
|
||||
|
||||
Controller.all.User = function UserController() {
|
||||
var controller = this, outfits = [], outfits_loaded = false;
|
||||
|
||||
function compareOutfits(a, b) {
|
||||
if(a.starred) {
|
||||
if(!b.starred) return -1;
|
||||
} else if(b.starred) {
|
||||
return 1;
|
||||
}
|
||||
if(a.name < b.name) return -1;
|
||||
else if(a.name == b.name) return 0;
|
||||
else return 1;
|
||||
}
|
||||
|
||||
function insertOutfit(outfit) {
|
||||
for(var i = 0; i < outfits.length; i++) {
|
||||
if(compareOutfits(outfit, outfits[i]) < 0) {
|
||||
outfits.splice(i, 0, outfit);
|
||||
controller.events.trigger('addOutfit', outfit, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
controller.events.trigger('addOutfit', outfit, outfits.length);
|
||||
outfits.push(outfit);
|
||||
}
|
||||
|
||||
function sortOutfits(outfits) {
|
||||
outfits.sort(compareOutfits);
|
||||
}
|
||||
|
||||
function yankOutfit(outfit) {
|
||||
var i;
|
||||
for(i = 0; i < outfits.length; i++) {
|
||||
if(outfit.id == outfits[i].id) {
|
||||
outfits.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
controller.events.trigger('removeOutfit', outfit, i);
|
||||
}
|
||||
|
||||
this.addOutfit = insertOutfit;
|
||||
|
||||
this.destroyOutfit = function (outfit) {
|
||||
outfit.destroy(function () {
|
||||
yankOutfit(outfit);
|
||||
});
|
||||
}
|
||||
|
||||
this.loadOutfits = function () {
|
||||
if(!outfits_loaded) {
|
||||
Outfit.loadForCurrentUser(function (new_outfits) {
|
||||
outfits = new_outfits;
|
||||
outfits_loaded = true;
|
||||
sortOutfits(outfits);
|
||||
controller.events.trigger('outfitsLoaded', outfits);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.renameOutfit = function (outfit, new_name) {
|
||||
var old_name = outfit.name;
|
||||
outfit.rename(new_name, function () {
|
||||
yankOutfit(outfit);
|
||||
insertOutfit(outfit);
|
||||
controller.events.trigger('outfitRenamed', outfit);
|
||||
}, function (outfit_copy, response) {
|
||||
outfit.name = old_name;
|
||||
controller.events.trigger('saveFailure', outfit_copy, response);
|
||||
});
|
||||
}
|
||||
|
||||
this.toggleOutfitStar = function (outfit) {
|
||||
outfit.toggleStar(function () {
|
||||
yankOutfit(outfit);
|
||||
insertOutfit(outfit);
|
||||
controller.events.trigger('outfitStarToggled', outfit);
|
||||
});
|
||||
}
|
||||
|
||||
this.updateOutfit = function (outfit) {
|
||||
for(var i = 0; i < outfits.length; i++) {
|
||||
if(outfits[i].id == outfit.id) {
|
||||
outfits[i] = outfit.clone();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var underscored_name;
|
||||
|
||||
for(var name in Controller.all) {
|
||||
|
@ -1196,13 +1325,13 @@ Wardrobe.getStandardView = function (options) {
|
|||
var outfit_events = ['updateWornItems', 'updateClosetItems', 'updateItemAssets', 'updatePetType', 'updatePetState'];
|
||||
for(var i = 0; i < outfit_events.length; i++) {
|
||||
(function (event) {
|
||||
wardrobe.outfit.bind(event, function (obj) {
|
||||
wardrobe.outfits.bind(event, function (obj) {
|
||||
log(event, obj);
|
||||
});
|
||||
})(outfit_events[i]);
|
||||
}
|
||||
|
||||
wardrobe.outfit.bind('petTypeNotFound', function (pet_type) {
|
||||
wardrobe.outfits.bind('petTypeNotFound', function (pet_type) {
|
||||
log(pet_type.toString() + ' not found');
|
||||
});
|
||||
}
|
||||
|
@ -1246,7 +1375,7 @@ Wardrobe.getStandardView = function (options) {
|
|||
var assets, assets_for_swf;
|
||||
if(update_pending_flash) return false;
|
||||
if(preview_swf && preview_swf.setAssets) {
|
||||
assets = wardrobe.outfit.getVisibleAssets();
|
||||
assets = wardrobe.outfits.getVisibleAssets();
|
||||
preview_swf.setAssets(assets);
|
||||
} else {
|
||||
update_pending_flash = true;
|
||||
|
@ -1303,10 +1432,11 @@ Wardrobe.getStandardView = function (options) {
|
|||
|
||||
// Get a copy of the visible assets, then sort them in ascending zone
|
||||
// order.
|
||||
var assets = wardrobe.outfit.getVisibleAssets().slice(0);
|
||||
var assets = wardrobe.outfits.getVisibleAssets().slice(0);
|
||||
assets.sort(function (a, b) {
|
||||
return a.depth - b.depth;
|
||||
});
|
||||
console.log(assets.mapProperty('id'));return;
|
||||
|
||||
for(var i = 0; i < assets.length; i++) {
|
||||
url += "," + encodeURIComponent(assets[i].imageURL(size));
|
||||
|
@ -1316,7 +1446,7 @@ Wardrobe.getStandardView = function (options) {
|
|||
}
|
||||
|
||||
this.updateAssets = function () {
|
||||
var assets = wardrobe.outfit.getVisibleAssets(), asset,
|
||||
var assets = wardrobe.outfits.getVisibleAssets(), asset,
|
||||
availableAssets = [];
|
||||
pendingAssets = {};
|
||||
pendingAssetsCount = 0;
|
||||
|
@ -1375,10 +1505,9 @@ Wardrobe.getStandardView = function (options) {
|
|||
for(var i in sizes) {
|
||||
if(!sizes.hasOwnProperty(i)) continue;
|
||||
size = sizes[i];
|
||||
size[2] = size[0] * size[1];
|
||||
inserted = false;
|
||||
for(var i in SIZES_SMALL_TO_LARGE) {
|
||||
if(SIZES_SMALL_TO_LARGE[i][2] > size[2]) {
|
||||
if(SIZES_SMALL_TO_LARGE[i][0] * SIZES_SMALL_TO_LARGE[i][1] > size[0] * size[1]) {
|
||||
SIZES_SMALL_TO_LARGE.splice(i, 0, size);
|
||||
inserted = true;
|
||||
break;
|
||||
|
@ -1476,9 +1605,9 @@ Wardrobe.getStandardView = function (options) {
|
|||
preview.adapter.updateAssets();
|
||||
}
|
||||
|
||||
wardrobe.outfit.bind('updateWornItems', updateAssets);
|
||||
wardrobe.outfit.bind('updateItemAssets', updateAssets);
|
||||
wardrobe.outfit.bind('updatePetState', updateAssets);
|
||||
wardrobe.outfits.bind('updateWornItems', updateAssets);
|
||||
wardrobe.outfits.bind('updateItemAssets', updateAssets);
|
||||
wardrobe.outfits.bind('updatePetState', updateAssets);
|
||||
|
||||
function useAdapter(name) {
|
||||
preview.adapter = new Adapter[name]();
|
||||
|
|
BIN
public/outfits/000/19/medium_thumb.png
Normal file
After Width: | Height: | Size: 121 KiB |
BIN
public/outfits/000/19/small_thumb.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
public/outfits/000/19/thumb.png
Normal file
After Width: | Height: | Size: 299 KiB |