beautiful outfits tab using thumbnails

This commit is contained in:
Emi Matchu 2012-07-27 03:21:22 -04:00
parent 374c7e6147
commit 249c493d25
7 changed files with 534 additions and 784 deletions

View file

@ -39,85 +39,6 @@ $outfit-content-inner-width: $outfit-content-width - $outfit-header-padding
border-bottom-color: white border-bottom-color: white
font-weight: bold 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)
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
=sidebar-view-child =sidebar-view-child
margin: margin:
left: $sidebar-unit-horizontal-padding left: $sidebar-unit-horizontal-padding
@ -471,20 +392,144 @@ body.outfits-edit
display: none display: none
text-align: left text-align: left
$outfit-inner-size: 110px
$outfit-margin: 1px
$outfit-outer-size: $outfit-inner-size + ($outfit-margin * 2)
> ul > ul
+sidebar-view-child +sidebar-view-child
background: image-url("loading.gif") no-repeat center top background: image-url("loading.gif") no-repeat center top
display: none display: none
font-family: $main-font font-family: $main-font
list-style: none list-style: none
margin: margin: 0 auto 1em
bottom: 1em
min-height: 16px min-height: 16px
> li width: $outfit-outer-size * 3
+outfit
&.loaded &.loaded
background: transparent background: transparent
> li
+inline-block
+outfit-star
height: $outfit-inner-size
margin: $outfit-margin
width: $outfit-inner-size
overflow: hidden
position: relative
$outfit-header-h-padding: 4px
$outfit-header-v-padding: 2px
$outfit-header-inner-width: $outfit-inner-size - (2 * $outfit-header-h-padding)
header, footer, .outfit-delete-confirmation
color: white
font-size: 85%
left: 0
padding: $outfit-header-v-padding $outfit-header-h-padding
position: absolute
width: $outfit-header-inner-width
z-index: 2
header, footer
background: black
background: rgba(0, 0, 0, 0.75)
header
+opacity(0.75)
bottom: 0
cursor: pointer
footer, .outfit-delete-confirmation
display: none
top: 0
.outfit-delete-confirmation
background: red
background: rgba(255, 50, 50, 0.75)
text-align: center
top: 0
span
font-weight: bold
$outfit-thumbnail-size: 150px
$outfit-thumbnail-offset: ($outfit-inner-size - $outfit-thumbnail-size) / 2
.outfit-thumbnail
+opacity(.5)
cursor: pointer
display: none
left: $outfit-thumbnail-offset
position: absolute
top: $outfit-thumbnail-offset
z-index: 1
.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
a
color: white
&: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
.outfit-thumbnail
display: block
&.loading
.outfit-star
background-image: image-url("loading_outfit_pane.gif")
#preview-outfits-not-logged-in #preview-outfits-not-logged-in
text-align: center text-align: center
@ -690,17 +735,6 @@ body.outfits-edit
+opacity(.5) +opacity(.5)
display: none 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 #new-outfit-name
font: inherit font: inherit
line-height: 1 line-height: 1
@ -720,7 +754,7 @@ body.outfits-edit
display: none display: none
form#save-outfit-form form#save-outfit-form
+outfit +outfit-star-shifted
display: none display: none
margin-right: 0 margin-right: 0
padding: 0 padding: 0

View file

@ -15,8 +15,6 @@
background-image: image-url("star.png") background-image: image-url("star.png")
&.loading .outfit-star &.loading .outfit-star
background-image: image-url("loading.gif") background-image: image-url("loading.gif")
&.loading.active .outfit-star
background-image: image-url("loading_current_outfit.gif")
=outfit-star-shifted =outfit-star-shifted
+outfit-star +outfit-star

View file

@ -160,18 +160,19 @@
%script#outfit-template{:type => 'text/x-jquery-tmpl'} %script#outfit-template{:type => 'text/x-jquery-tmpl'}
<li class="outfit-${id}{{if starred}} starred{{/if}}"> <li class="outfit-${id}{{if starred}} starred{{/if}}">
%header %header
%button.outfit-delete &times;
.outfit-star .outfit-star
%h4 ${name} %span.outfit-name ${name}
%a.outfit-rename-button{:href => '#'} rename
%form.outfit-rename-form %form.outfit-rename-form
%input.outfit-rename-field{:type => 'text'} %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
%img.outfit-thumbnail
.outfit-delete-confirmation .outfit-delete-confirmation
%span Delete forever? %span Delete?
%a.outfit-delete-confirmation-yes{:href => '#'} yes %a.outfit-delete-confirmation-yes{:href => '#'} yes
\/ \/
%a.outfit-delete-confirmation-no{:href => '#'} no, thanks %a.outfit-delete-confirmation-no{:href => '#'} no
</li> </li>
- content_for :javascripts do - content_for :javascripts do
= include_javascript_libraries :jquery, :swfobject, :jquery_tmpl = include_javascript_libraries :jquery, :swfobject, :jquery_tmpl

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -519,12 +519,32 @@ View.Outfits = function (wardrobe) {
/* Outfits list */ /* 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'); $('#outfit-template').template('outfitTemplate');
wardrobe.outfits.bind('outfitsLoaded', function (outfits) { wardrobe.outfits.bind('outfitsLoaded', function (outfits) {
var outfit_els = $.tmpl('outfitTemplate', outfits); var outfit_els = $.tmpl('outfitTemplate', outfits);
outfits_list_el.html('').append(outfit_els).addClass('loaded'); outfits_list_el.html('').append(outfit_els).addClass('loaded');
updateActiveOutfit(); updateActiveOutfit();
for(var i = 0; i < outfits.length; i++) {
listSubscribeToImage(outfits[i]);
}
}); });
wardrobe.outfits.bind('addOutfit', function (outfit, i) { wardrobe.outfits.bind('addOutfit', function (outfit, i) {
@ -536,22 +556,27 @@ View.Outfits = function (wardrobe) {
outfit_el.appendTo(outfits_list_el); outfit_el.appendTo(outfits_list_el);
} }
updateActiveOutfit(); updateActiveOutfit();
outfit_el.hide().show('normal');
var naturalWidth = outfit_el.width();
log("Natural width is", naturalWidth);
outfit_el.width(0).animate({width: naturalWidth}, 'normal');
listSubscribeToImage(outfit);
}); });
wardrobe.outfits.bind('removeOutfit', function (outfit, i) { wardrobe.outfits.bind('removeOutfit', function (outfit, i) {
var outfit_el = outfits_list_el.children().not('.hiding').eq(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 () { $('#preview-outfits li header, #preview-outfits li .outfit-thumbnail').live('click', function () {
wardrobe.outfits.load($(this).tmplItem().data.id); wardrobe.outfits.load($(this).tmplItem().data.id);
}); });
$('a.outfit-rename-button').live('click', function (e) { $('a.outfit-rename-button').live('click', function (e) {
e.preventDefault(); e.preventDefault();
var li = $(this).closest('li').addClass('renaming'), 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(); li.find('input.outfit-rename-field').val(name).focus();
}); });
@ -578,7 +603,8 @@ View.Outfits = function (wardrobe) {
this.blur(); this.blur();
}); });
$('button.outfit-delete').live('click', function (e) { $('a.outfit-delete').live('click', function (e) {
e.stopPropagation();
e.preventDefault(); e.preventDefault();
$(this).closest('li').addClass('confirming-deletion'); $(this).closest('li').addClass('confirming-deletion');
}); });
@ -597,7 +623,8 @@ View.Outfits = function (wardrobe) {
$(this).closest('li').removeClass('confirming-deletion'); $(this).closest('li').removeClass('confirming-deletion');
}); });
stars.live('click', function () { stars.live('click', function (e) {
e.stopPropagation();
var el = $(this); var el = $(this);
el.closest('li').startLoading(); el.closest('li').startLoading();
wardrobe.outfits.toggleOutfitStar(el.tmplItem().data); wardrobe.outfits.toggleOutfitStar(el.tmplItem().data);
@ -646,6 +673,32 @@ View.Outfits = function (wardrobe) {
} }
}); });
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').
children('img.outfit-thumbnail').attr('src', src);
}
});
/* Sharing */ /* Sharing */
var sharing = new function Sharing() { var sharing = new function Sharing() {
@ -697,19 +750,17 @@ View.Outfits = function (wardrobe) {
var image_subscription = null; var image_subscription = null;
function unsubscribeFromImage() { function unsubscribeFromImage() {
if(image_subscription !== null) {
wardrobe.image_subscriptions.unsubscribe(image_subscription); wardrobe.image_subscriptions.unsubscribe(image_subscription);
image_subscription = null; image_subscription = null;
} }
}
function subscribeToImage(outfit) { function subscribeToImage(outfit) {
image_subscription = wardrobe.image_subscriptions.subscribe(outfit); image_subscription = wardrobe.image_subscriptions.subscribe(outfit);
} }
function subscribeToImageIfVisible() { function subscribeToImageIfVisible(outfit) {
if(current_shared_outfit && sidebar_el.hasClass('sharing')) { if(outfit && sidebar_el.hasClass('sharing')) {
subscribeToImage(current_shared_outfit); subscribeToImage(outfit);
} }
} }
@ -738,7 +789,7 @@ View.Outfits = function (wardrobe) {
urls.large_image = pathToUrl(outfit.image_versions.large); urls.large_image = pathToUrl(outfit.image_versions.large);
formatUrls(); formatUrls();
WRAPPER.removeClass('thumbnail-available'); WRAPPER.removeClass('thumbnail-available');
subscribeToImageIfVisible(); subscribeToImageIfVisible(current_shared_outfit);
} }
WRAPPER.addClass('urls-loaded'); WRAPPER.addClass('urls-loaded');
} }
@ -752,7 +803,7 @@ View.Outfits = function (wardrobe) {
} }
this.onShow = function () { this.onShow = function () {
subscribeToImageIfVisible(); subscribeToImageIfVisible(wardrobe.outfits.getOutfit());
} }
function formatUrls() { function formatUrls() {
@ -775,19 +826,21 @@ View.Outfits = function (wardrobe) {
} }
wardrobe.image_subscriptions.bind('imageEnqueued', function (outfit) { wardrobe.image_subscriptions.bind('imageEnqueued', function (outfit) {
if(outfit.id == current_shared_outfit.id) {
log("Sharing thumbnail enqueued for outfit", outfit); log("Sharing thumbnail enqueued for outfit", outfit);
WRAPPER.removeClass('thumbnail-loaded'); WRAPPER.removeClass('thumbnail-loaded');
}
}); });
wardrobe.image_subscriptions.bind('imageReady', function (outfit) { wardrobe.image_subscriptions.bind('imageReady', function (outfit) {
if(outfit.id == current_shared_outfit.id) {
log("Sharing thumbnail ready for outfit", outfit); log("Sharing thumbnail ready for outfit", outfit);
var src = outfit.image_versions.small + '?' + outfit.image_layers_hash; var src = outfit.image_versions.small + '?' + outfit.image_layers_hash;
thumbnail_el.attr('src', src); thumbnail_el.attr('src', src);
WRAPPER.addClass('thumbnail-loaded'); WRAPPER.addClass('thumbnail-loaded');
WRAPPER.addClass('thumbnail-available'); WRAPPER.addClass('thumbnail-available');
image_subscription = null; unsubscribeFromImage(outfit);
}
}); });
wardrobe.outfits.bind('updateSuccess', function (outfit) { wardrobe.outfits.bind('updateSuccess', function (outfit) {

View file

@ -1137,25 +1137,41 @@ function Wardrobe() {
} }
this.subscribe = function (outfit) { this.subscribe = function (outfit) {
if(!(outfit.id in outfitSubscriptionTotals)) { if(outfit.id in outfitSubscriptionTotals) {
outfitSubscriptionTotals[outfit.id] = 0; // 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; outfitSubscriptionTotals[outfit.id] += 1;
} else {
// This is a new subscription!
outfitSubscriptionTotals[outfit.id] = 1;
if(outfit.image_enqueued) { if(outfit.image_enqueued) {
// If the image is enqueued, trigger that event and start checking. // If the image is enqueued, trigger that event and start checking.
controller.events.trigger('imageEnqueued', outfit); controller.events.trigger('imageEnqueued', outfit);
checkSubscription(outfit); checkSubscription(outfit);
return outfit;
} else { } else {
// Otherwise, never bother checking: skip straight to the ready phase. // Otherwise, never bother checking: skip straight to the ready phase.
controller.events.trigger('imageReady', outfit); // Give it an instant timeout so that we're sure the consumer is ready
return null; // 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) { this.unsubscribe = function (outfit) {
if(outfit && outfit.id in outfitSubscriptionTotals) {
if(outfitSubscriptionTotals[outfit.id] > 1) {
outfitSubscriptionTotals[outfit.id] -= 1; outfitSubscriptionTotals[outfit.id] -= 1;
} else {
delete outfitSubscriptionTotals[outfit.id];
}
}
} }
} }

File diff suppressed because it is too large Load diff