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
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
margin:
left: $sidebar-unit-horizontal-padding
@ -471,19 +392,143 @@ body.outfits-edit
display: none
text-align: left
$outfit-inner-size: 110px
$outfit-margin: 1px
$outfit-outer-size: $outfit-inner-size + ($outfit-margin * 2)
> ul
+sidebar-view-child
background: image-url("loading.gif") no-repeat center top
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
+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
text-align: center
@ -690,17 +735,6 @@ 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
@ -720,10 +754,10 @@ body.outfits-edit
display: none
form#save-outfit-form
+outfit
+outfit-star-shifted
display: none
margin-right: 0
padding: 0
padding: 0
.outfit-star, input, button
+inline-block

View file

@ -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

View file

@ -160,18 +160,19 @@
%script#outfit-template{:type => 'text/x-jquery-tmpl'}
<li class="outfit-${id}{{if starred}} starred{{/if}}">
%header
%button.outfit-delete &times;
.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
%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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -518,6 +518,22 @@ 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');
@ -525,6 +541,10 @@ View.Outfits = function (wardrobe) {
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.outfits.bind('addOutfit', function (outfit, i) {
@ -536,22 +556,27 @@ View.Outfits = function (wardrobe) {
outfit_el.appendTo(outfits_list_el);
}
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) {
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);
});
$('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();
});
@ -578,7 +603,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');
});
@ -597,7 +623,8 @@ 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.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 */
var sharing = new function Sharing() {
@ -697,19 +750,17 @@ View.Outfits = function (wardrobe) {
var image_subscription = null;
function unsubscribeFromImage() {
if(image_subscription !== null) {
wardrobe.image_subscriptions.unsubscribe(image_subscription);
image_subscription = null;
}
wardrobe.image_subscriptions.unsubscribe(image_subscription);
image_subscription = null;
}
function subscribeToImage(outfit) {
image_subscription = wardrobe.image_subscriptions.subscribe(outfit);
}
function subscribeToImageIfVisible() {
if(current_shared_outfit && sidebar_el.hasClass('sharing')) {
subscribeToImage(current_shared_outfit);
function subscribeToImageIfVisible(outfit) {
if(outfit && sidebar_el.hasClass('sharing')) {
subscribeToImage(outfit);
}
}
@ -738,7 +789,7 @@ View.Outfits = function (wardrobe) {
urls.large_image = pathToUrl(outfit.image_versions.large);
formatUrls();
WRAPPER.removeClass('thumbnail-available');
subscribeToImageIfVisible();
subscribeToImageIfVisible(current_shared_outfit);
}
WRAPPER.addClass('urls-loaded');
}
@ -752,7 +803,7 @@ View.Outfits = function (wardrobe) {
}
this.onShow = function () {
subscribeToImageIfVisible();
subscribeToImageIfVisible(wardrobe.outfits.getOutfit());
}
function formatUrls() {
@ -775,19 +826,21 @@ View.Outfits = function (wardrobe) {
}
wardrobe.image_subscriptions.bind('imageEnqueued', function (outfit) {
log("Sharing thumbnail enqueued for outfit", outfit);
WRAPPER.removeClass('thumbnail-loaded');
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) {
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');
image_subscription = null;
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) {

View file

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

File diff suppressed because it is too large Load diff