forked from OpenNeo/impress
beautiful outfits tab using thumbnails
This commit is contained in:
parent
374c7e6147
commit
249c493d25
7 changed files with 534 additions and 784 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -160,18 +160,19 @@
|
|||
%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
|
||||
%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
|
||||
|
|
BIN
public/images/loading_outfit_pane.gif
Normal file
BIN
public/images/loading_outfit_pane.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
|
@ -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) {
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue