1194 lines
40 KiB
JavaScript
1194 lines
40 KiB
JavaScript
|
/*
|
||
|
* Handle the thumbnail view, and navigation for the main view.
|
||
|
*
|
||
|
* Handle a large number (thousands) of entries cleanly. Thumbnail nodes are created
|
||
|
* as needed, and destroyed when they scroll off screen. This gives us constant
|
||
|
* startup time, loads thumbnails on demand, allows preloading thumbnails in advance
|
||
|
* by creating more nodes in advance, and keeps memory usage constant.
|
||
|
*/
|
||
|
ThumbnailView = function(container, view)
|
||
|
{
|
||
|
this.container = container;
|
||
|
this.view = view;
|
||
|
this.post_ids = [];
|
||
|
this.post_frames = [];
|
||
|
this.expanded_post_idx = null;
|
||
|
this.centered_post_idx = null;
|
||
|
this.centered_post_offset = 0;
|
||
|
this.last_mouse_x = 0;
|
||
|
this.last_mouse_y = 0;
|
||
|
this.thumb_container_shown = true;
|
||
|
this.allow_wrapping = true;
|
||
|
this.thumb_preload_container = new PreloadContainer();
|
||
|
this.unused_thumb_pool = [];
|
||
|
|
||
|
/* The [first, end) range of posts that are currently inside .post-browser-posts. */
|
||
|
this.posts_populated = [0, 0];
|
||
|
|
||
|
document.on("DOMMouseScroll", this.document_mouse_wheel_event.bindAsEventListener(this));
|
||
|
document.on("mousewheel", this.document_mouse_wheel_event.bindAsEventListener(this));
|
||
|
|
||
|
document.on("viewer:displayed-image-loaded", this.displayed_image_loaded_event.bindAsEventListener(this));
|
||
|
document.on("viewer:set-active-post", function(e) {
|
||
|
var post_id_and_frame = [e.memo.post_id, e.memo.post_frame];
|
||
|
this.set_active_post(post_id_and_frame, e.memo.lazy, e.memo.center_thumbs);
|
||
|
}.bindAsEventListener(this));
|
||
|
document.on("viewer:show-next-post", function(e) { this.show_next_post(e.memo.prev); }.bindAsEventListener(this));
|
||
|
|
||
|
document.on("viewer:scroll", function(e) { this.scroll(e.memo.left); }.bindAsEventListener(this));
|
||
|
document.on("viewer:set-thumb-bar", function(e) {
|
||
|
if(e.memo.toggle)
|
||
|
this.show_thumb_bar(!this.thumb_container_shown);
|
||
|
else
|
||
|
this.show_thumb_bar(e.memo.set);
|
||
|
}.bindAsEventListener(this));
|
||
|
document.on("viewer:loaded-posts", this.loaded_posts_event.bindAsEventListener(this));
|
||
|
|
||
|
this.hashchange_post_id = this.hashchange_post_id.bind(this);
|
||
|
UrlHash.observe("post-id", this.hashchange_post_id);
|
||
|
UrlHash.observe("post-frame", this.hashchange_post_id);
|
||
|
|
||
|
new DragElement(this.container, { ondrag: this.container_ondrag.bind(this) });
|
||
|
|
||
|
Element.on(window, "resize", this.window_resize_event.bindAsEventListener(this));
|
||
|
|
||
|
this.container.on("mousemove", this.container_mousemove_event.bindAsEventListener(this));
|
||
|
this.container.on("mouseover", this.container_mouseover_event.bindAsEventListener(this));
|
||
|
this.container.on("mouseout", this.container_mouseout_event.bindAsEventListener(this));
|
||
|
this.container.on("click", this.container_click_event.bindAsEventListener(this));
|
||
|
this.container.on("dblclick", ".post-thumb,.browser-thumb-hover-overlay",
|
||
|
this.container_dblclick_event.bindAsEventListener(this));
|
||
|
|
||
|
/* Prevent the default behavior of left-clicking on the expanded thumbnail overlay. It's
|
||
|
* handled by container_click_event. */
|
||
|
this.container.down(".browser-thumb-hover-overlay").on("click", function(event) {
|
||
|
if(event.isLeftClick())
|
||
|
event.preventDefault();
|
||
|
}.bindAsEventListener(this));
|
||
|
|
||
|
/*
|
||
|
* For Android browsers, we're set to 150 DPI, which (in theory) scales us to a consistent UI size
|
||
|
* based on the screen DPI. This means that we can determine the physical screen size from the
|
||
|
* window resolution: 150x150 is 1"x1". Set a thumbnail scale based on this. On a 320x480 HVGA
|
||
|
* phone screen the thumbnails are about 2x too big, so set thumb_scale to 0.5.
|
||
|
*
|
||
|
* For iOS browsers, there's no way to set the viewport based on the DPI, so it's fixed at 1x.
|
||
|
* (Note that on Retina screens the browser lies: even though we request 1x, it's actually at
|
||
|
* 0.5x and our screen dimensions work as if we're on the lower-res iPhone screen. We can mostly
|
||
|
* ignore this.) CSS inches aren't implemented (the DPI is fixed at 96), so that doesn't help us.
|
||
|
* Fall back on special-casing individual iOS devices.
|
||
|
*/
|
||
|
this.config = { };
|
||
|
if(navigator.userAgent.indexOf("iPad") != -1)
|
||
|
{
|
||
|
this.config.thumb_scale = 1.0;
|
||
|
}
|
||
|
else if(navigator.userAgent.indexOf("iPhone") != -1 || navigator.userAgent.indexOf("iPod") != -1)
|
||
|
{
|
||
|
this.config.thumb_scale = 0.5;
|
||
|
}
|
||
|
else if(navigator.userAgent.indexOf("Android") != -1)
|
||
|
{
|
||
|
/* We may be in landscape or portrait; use out the narrower dimension. */
|
||
|
var width = Math.min(window.innerWidth, window.innerHeight);
|
||
|
|
||
|
/* Scale a 320-width screen to 0.5, up to 1.0 for a 640-width screen. Remember
|
||
|
* that this width is already scaled by the DPI of the screen due to target-densityDpi,
|
||
|
* so these numbers aren't actually real pixels, and this scales based on the DPI
|
||
|
* and size of the screen rather than the pixel count. */
|
||
|
this.config.thumb_scale = scale(width, 320, 640, 0.5, 1.0);
|
||
|
debug("Unclamped thumb scale: " + this.config.thumb_scale);
|
||
|
|
||
|
/* Clamp to [0.5,1.0]. */
|
||
|
this.config.thumb_scale = Math.min(this.config.thumb_scale, 1.0);
|
||
|
this.config.thumb_scale = Math.max(this.config.thumb_scale, 0.5);
|
||
|
|
||
|
debug("startup, window size: " + window.innerWidth + "x" + window.innerHeight);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Unknown device, or not a mobile device. */
|
||
|
this.config.thumb_scale = 1.0;
|
||
|
}
|
||
|
debug("Thumb scale: " + this.config.thumb_scale);
|
||
|
|
||
|
this.config_changed();
|
||
|
|
||
|
/* Send the initial viewer:thumb-bar-changed event. */
|
||
|
this.thumb_container_shown = false;
|
||
|
this.show_thumb_bar(true);
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.window_resize_event = function(e)
|
||
|
{
|
||
|
if(e.stopped)
|
||
|
return;
|
||
|
if(this.thumb_container_shown)
|
||
|
this.center_on_post_for_scroll(this.centered_post_idx);
|
||
|
}
|
||
|
|
||
|
/* Show the given posts. If extending is true, post_ids are meant to extend a previous
|
||
|
* search; attempt to continue where we left off. */
|
||
|
ThumbnailView.prototype.loaded_posts_event = function(event)
|
||
|
{
|
||
|
var post_ids = event.memo.post_ids;
|
||
|
|
||
|
var old_post_ids = this.post_ids;
|
||
|
var old_centered_post_idx = this.centered_post_idx;
|
||
|
this.remove_all_posts();
|
||
|
|
||
|
/* Filter blacklisted posts. */
|
||
|
post_ids = post_ids.reject(Post.is_blacklisted);
|
||
|
|
||
|
this.post_ids = [];
|
||
|
this.post_frames = [];
|
||
|
|
||
|
for(var i = 0; i < post_ids.length; ++i)
|
||
|
{
|
||
|
var post_id = post_ids[i];
|
||
|
var post = Post.posts.get(post_id);
|
||
|
if(post.frames.length > 0)
|
||
|
{
|
||
|
for(var frame_idx = 0; frame_idx < post.frames.length; ++frame_idx)
|
||
|
{
|
||
|
this.post_ids.push(post_id);
|
||
|
this.post_frames.push(frame_idx);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.post_ids.push(post_id);
|
||
|
this.post_frames.push(-1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.allow_wrapping = !event.memo.can_be_extended_further;
|
||
|
|
||
|
/* Show the results box or "no results". Do this before updating the results box to make sure
|
||
|
* the results box isn't hidden when we update, which will make offsetLeft values inside it zero
|
||
|
* and break things. If the reason we have no posts is because we didn't do a search at all,
|
||
|
* don't show no-results. */
|
||
|
this.container.down(".post-browser-no-results").show(event.memo.tags != null && this.post_ids.length == 0);
|
||
|
this.container.down(".post-browser-posts").show(this.post_ids.length != 0);
|
||
|
|
||
|
if(event.memo.extending)
|
||
|
{
|
||
|
/*
|
||
|
* We're extending a previous search with more posts. The new post list we get may
|
||
|
* not line up with the old one: the post we're focused on may no longer be in the
|
||
|
* search, or may be at a different index.
|
||
|
*
|
||
|
* Find a nearby post in the new results. Start searching at the post we're already
|
||
|
* centered on. If that doesn't match, move outwards from there. Only look forward
|
||
|
* a little bit, or we may match a post that was never seen and jump forward too far
|
||
|
* in the results.
|
||
|
*/
|
||
|
var post_id_search_order = sort_array_by_distance(old_post_ids.slice(0, old_centered_post_idx+3), old_centered_post_idx);
|
||
|
var initial_post_id = null;
|
||
|
for(var i = 0; i < post_id_search_order.length; ++i)
|
||
|
{
|
||
|
var post_id_to_search = post_id_search_order[i];
|
||
|
var post = Post.posts.get(post_id_to_search);
|
||
|
if(post != null)
|
||
|
{
|
||
|
initial_post_id = post.id;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
debug("center-on-" + initial_post_id);
|
||
|
|
||
|
/* If we didn't find anything that matched, go back to the start. */
|
||
|
if(initial_post_id == null)
|
||
|
{
|
||
|
this.centered_post_offset = 0;
|
||
|
initial_post_id = new_post_ids[0];
|
||
|
}
|
||
|
|
||
|
var center_on_post_idx = this.post_ids.indexOf(initial_post_id);
|
||
|
this.center_on_post_for_scroll(center_on_post_idx);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/*
|
||
|
* A new search has completed.
|
||
|
*
|
||
|
* results_mode can be one of the following:
|
||
|
*
|
||
|
* "center-on-first"
|
||
|
* Don't change the active post. Center the results on the first result. This is used
|
||
|
* when performing a search by clicking on a tag, where we don't want to center on the
|
||
|
* post we're on (since it'll put us at some random spot in the results when the user
|
||
|
* probably wants to browse from the beginning), and we don't want to change the displayed
|
||
|
* post either.
|
||
|
*
|
||
|
* "center-on-current"
|
||
|
* Don't change the active post. Center the results on the existing current item,
|
||
|
* if possible. This is used when we want to show a new search without disrupting the
|
||
|
* shown post, such as the "child posts" link in post info, and when loading the initial
|
||
|
* URL hash when we start up.
|
||
|
*
|
||
|
* "jump-to-first"
|
||
|
* Set the active post to the first result, and center on it. This is used after making
|
||
|
* a search in the tags box.
|
||
|
*/
|
||
|
var results_mode = event.memo.load_options.results_mode || "center-on-current";
|
||
|
|
||
|
var initial_post_id_and_frame;
|
||
|
if(results_mode == "center-on-first" || results_mode == "jump-to-first")
|
||
|
initial_post_id_and_frame = [this.post_ids[0], this.post_frames[0]];
|
||
|
else
|
||
|
initial_post_id_and_frame = this.get_current_post_id_and_frame();
|
||
|
|
||
|
var center_on_post_idx = this.get_post_idx(initial_post_id_and_frame);
|
||
|
if(center_on_post_idx == null)
|
||
|
center_on_post_idx = 0;
|
||
|
|
||
|
this.centered_post_offset = 0;
|
||
|
this.center_on_post_for_scroll(center_on_post_idx);
|
||
|
|
||
|
/* If no post is currently displayed and we just completed a search, set the current post.
|
||
|
* This happens when first initializing; we wait for the first search to complete to retrieve
|
||
|
* info about the post we're starting on, instead of making a separate query. */
|
||
|
if(results_mode == "jump-to-first" || this.view.wanted_post_id == null)
|
||
|
this.set_active_post(initial_post_id_and_frame, false, false, true);
|
||
|
}
|
||
|
|
||
|
if(event.memo.tags == null)
|
||
|
{
|
||
|
/* If tags is null then no search has been done, which means we're on a URL
|
||
|
* with a post ID and no search, eg. "/post/browse#12345". Hide the thumb
|
||
|
* bar, so we'll just show the post. */
|
||
|
this.show_thumb_bar(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.container_ondrag = function(e)
|
||
|
{
|
||
|
this.centered_post_offset -= e.dX;
|
||
|
this.center_on_post_for_scroll(this.centered_post_idx);
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.container_mouseover_event = function(event)
|
||
|
{
|
||
|
var li = $(event.target);
|
||
|
if(!li.hasClassName(".post-thumb"))
|
||
|
li = li.up(".post-thumb");
|
||
|
if(li)
|
||
|
this.expand_post(li.post_idx);
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.container_mouseout_event = function(event)
|
||
|
{
|
||
|
/* If the mouse is leaving the hover overlay, hide it. */
|
||
|
var target = $(event.target);
|
||
|
if(!target.hasClassName(".browser-thumb-hover-overlay"))
|
||
|
target = target.up(".browser-thumb-hover-overlay");
|
||
|
if(target)
|
||
|
this.expand_post(null);
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.hashchange_post_id = function()
|
||
|
{
|
||
|
var post_id_and_frame = this.get_current_post_id_and_frame();
|
||
|
if(post_id_and_frame[0] == null)
|
||
|
return;
|
||
|
|
||
|
/* If we're already displaying this post, ignore the hashchange. Don't center on the
|
||
|
* post if this is just a side-effect of clicking a post, rather than the user actually
|
||
|
* changing the hash. */
|
||
|
var post_id = post_id_and_frame[0];
|
||
|
var post_frame = post_id_and_frame[1];
|
||
|
if(post_id == this.view.displayed_post_id &&
|
||
|
post_frame == this.view.displayed_post_frame)
|
||
|
{
|
||
|
// debug("ignored-hashchange");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var new_post_idx = this.get_post_idx(post_id_and_frame);
|
||
|
this.centered_post_offset = 0;
|
||
|
this.center_on_post_for_scroll(new_post_idx);
|
||
|
this.set_active_post(post_id_and_frame, false, false, true);
|
||
|
}
|
||
|
|
||
|
/* Search for the given post ID and frame in the current search results, and return its
|
||
|
* index. If the given post isn't in post_ids, return null. */
|
||
|
ThumbnailView.prototype.get_post_idx = function(post_id_and_frame)
|
||
|
{
|
||
|
var post_id = post_id_and_frame[0];
|
||
|
var post_frame = post_id_and_frame[1];
|
||
|
|
||
|
var post_idx = this.post_ids.indexOf(post_id);
|
||
|
if(post_idx == -1)
|
||
|
return null;
|
||
|
if(post_frame == -1)
|
||
|
return post_idx;
|
||
|
|
||
|
/* A post-frame is specified. Search for a matching post-id and post-frame. We assume
|
||
|
* here that all frames for a post are grouped together in post_ids. */
|
||
|
var post_frame_idx = post_idx;
|
||
|
while(post_frame_idx < this.post_ids.length && this.post_ids[post_frame_idx] == post_id)
|
||
|
{
|
||
|
if(this.post_frames[post_frame_idx] == post_frame)
|
||
|
return post_frame_idx;
|
||
|
++post_frame_idx;
|
||
|
}
|
||
|
|
||
|
/* We found a matching post, but not a matching frame. Return the post. */
|
||
|
return post_idx;
|
||
|
}
|
||
|
|
||
|
/* Return the post and frame that's currently being displayed in the main view, based
|
||
|
* on the URL hash. If no post is displayed and no search results are available,
|
||
|
* return [null, null]. */
|
||
|
ThumbnailView.prototype.get_current_post_id_and_frame = function()
|
||
|
{
|
||
|
var post_id = UrlHash.get("post-id");
|
||
|
if(post_id == null)
|
||
|
{
|
||
|
if(this.post_ids.length == 0)
|
||
|
return [null, null];
|
||
|
else
|
||
|
return [this.post_ids[0], this.post_frames[0]];
|
||
|
}
|
||
|
post_id = parseInt(post_id);
|
||
|
|
||
|
var post_frame = UrlHash.get("post-frame");
|
||
|
|
||
|
// If no frame is set, attempt to resolve the post_frame we'll display, if the post data
|
||
|
// is already loaded. Otherwise, post_frame will remain null.
|
||
|
if(post_frame == null)
|
||
|
post_frame = this.view.get_default_post_frame(post_id);
|
||
|
|
||
|
return [post_id, post_frame];
|
||
|
}
|
||
|
|
||
|
/* Track the mouse cursor when it's within the container. */
|
||
|
ThumbnailView.prototype.container_mousemove_event = function(e)
|
||
|
{
|
||
|
var x = e.pointerX() - document.documentElement.scrollLeft;
|
||
|
var y = e.pointerY() - document.documentElement.scrollTop;
|
||
|
this.last_mouse_x = x;
|
||
|
this.last_mouse_y = y;
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.document_mouse_wheel_event = function(event)
|
||
|
{
|
||
|
event.stop();
|
||
|
|
||
|
var val;
|
||
|
if(event.wheelDelta)
|
||
|
{
|
||
|
val = event.wheelDelta;
|
||
|
} else if (event.detail) {
|
||
|
val = -event.detail;
|
||
|
}
|
||
|
|
||
|
if(this.thumb_container_shown)
|
||
|
document.fire("viewer:scroll", { left: val >= 0 });
|
||
|
else
|
||
|
document.fire("viewer:show-next-post", { prev: val >= 0 });
|
||
|
}
|
||
|
|
||
|
/* Set the post that's shown in the view. The thumbs will be centered on the post
|
||
|
* if center_thumbs is true. See BrowserView.prototype.set_post for an explanation
|
||
|
* of no_hash_change. */
|
||
|
ThumbnailView.prototype.set_active_post = function(post_id_and_frame, lazy, center_thumbs, no_hash_change, replace_history)
|
||
|
{
|
||
|
/* If no post is specified, do nothing. This will happen if a search returns
|
||
|
* no results. */
|
||
|
if(post_id_and_frame[0] == null)
|
||
|
return;
|
||
|
|
||
|
this.view.set_post(post_id_and_frame[0], post_id_and_frame[1], lazy, no_hash_change, replace_history);
|
||
|
|
||
|
if(center_thumbs)
|
||
|
{
|
||
|
var post_idx = this.get_post_idx(post_id_and_frame);
|
||
|
this.centered_post_offset = 0;
|
||
|
this.center_on_post_for_scroll(post_idx);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.set_active_post_idx = function(post_idx, lazy, center_thumbs, no_hash_change, replace_history)
|
||
|
{
|
||
|
if(post_idx == null)
|
||
|
return;
|
||
|
|
||
|
var post_id = this.post_ids[post_idx];
|
||
|
var post_frame = this.post_frames[post_idx];
|
||
|
this.set_active_post([post_id, post_frame], lazy, center_thumbs, no_hash_change, replace_history);
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.show_next_post = function(prev)
|
||
|
{
|
||
|
if(this.post_ids.length == 0)
|
||
|
return;
|
||
|
|
||
|
var current_idx = this.get_post_idx([this.view.wanted_post_id, this.view.wanted_post_frame]);
|
||
|
|
||
|
/* If the displayed post isn't in the thumbnails and we're changing posts, start
|
||
|
* at the beginning. */
|
||
|
if(current_idx == null)
|
||
|
current_idx = 0;
|
||
|
|
||
|
var add = prev? -1:+1;
|
||
|
if(this.post_frames[current_idx] != this.view.wanted_post_frame && add == +1)
|
||
|
{
|
||
|
/*
|
||
|
* We didn't find an exact match for the frame we're displaying, which usually means
|
||
|
* we viewed a post frame, and then the user changed the view to the main post, and
|
||
|
* the main post isn't in the thumbnails.
|
||
|
*
|
||
|
* It's strange to be on the main post, to hit pgdn, and to end up on the second frame
|
||
|
* because the nearest match was the first frame. Instead, we should end up on the first
|
||
|
* frame. To do that, just don't add anything to the index.
|
||
|
*/
|
||
|
debug("Snapped the display to the nearest frame");
|
||
|
if(add == +1)
|
||
|
add = 0;
|
||
|
}
|
||
|
|
||
|
var new_idx = current_idx;
|
||
|
new_idx += add;
|
||
|
|
||
|
new_idx += this.post_ids.length;
|
||
|
new_idx %= this.post_ids.length;
|
||
|
|
||
|
var wrapped = (prev && new_idx > current_idx) || (!prev && new_idx < current_idx);
|
||
|
if(wrapped)
|
||
|
{
|
||
|
/* Only allow wrapping over the edge if we've already expanded the results. */
|
||
|
if(!this.allow_wrapping)
|
||
|
return;
|
||
|
if(!this.thumb_container_shown && prev)
|
||
|
notice("Continued from the end");
|
||
|
else if(!this.thumb_container_shown && !prev)
|
||
|
notice("Starting over from the beginning");
|
||
|
}
|
||
|
|
||
|
this.set_active_post_idx(new_idx, true, true, false, true);
|
||
|
}
|
||
|
|
||
|
/* Scroll the thumbnail view left or right. Don't change the displayed post. */
|
||
|
ThumbnailView.prototype.scroll = function(left)
|
||
|
{
|
||
|
/* There's no point in scrolling the list if it's not visible. */
|
||
|
if(!this.thumb_container_shown)
|
||
|
return;
|
||
|
var new_idx = this.centered_post_idx;
|
||
|
|
||
|
/* If we're not centered on the post, and we're moving towards the center,
|
||
|
* don't jump past the post. */
|
||
|
if(this.centered_post_offset > 0 && left)
|
||
|
;
|
||
|
else if(this.centered_post_offset < 0 && !left)
|
||
|
;
|
||
|
else
|
||
|
new_idx += (left? -1:+1);
|
||
|
|
||
|
// Snap to the nearest post.
|
||
|
this.centered_post_offset = 0;
|
||
|
|
||
|
/* Wrap the new index. */
|
||
|
if(new_idx < 0)
|
||
|
{
|
||
|
/* Only allow scrolling over the left edge if we've already expanded the results. */
|
||
|
if(!this.allow_wrapping)
|
||
|
new_idx = 0;
|
||
|
else
|
||
|
new_idx = this.post_ids.length - 1;
|
||
|
}
|
||
|
else if(new_idx >= this.post_ids.length)
|
||
|
{
|
||
|
if(!this.allow_wrapping)
|
||
|
new_idx = this.post_ids.length - 1;
|
||
|
else
|
||
|
new_idx = 0;
|
||
|
}
|
||
|
|
||
|
this.center_on_post_for_scroll(new_idx);
|
||
|
}
|
||
|
|
||
|
/* Hide the hovered post, if any, call center_on_post(post_idx), then hover over the correct post again. */
|
||
|
ThumbnailView.prototype.center_on_post_for_scroll = function(post_idx)
|
||
|
{
|
||
|
if(this.thumb_container_shown)
|
||
|
this.expand_post(null);
|
||
|
|
||
|
this.center_on_post(post_idx);
|
||
|
|
||
|
/*
|
||
|
* Now that we've re-centered, we need to expand the correct image. Usually, we can just
|
||
|
* wait for the mouseover event to fire, since we hid the expanded thumb overlay and the
|
||
|
* image underneith it is now under the mouse. However, browsers are badly broken here.
|
||
|
* Opera doesn't fire mouseover events when the element under the cursor is hidden. FF
|
||
|
* fires the mouseover on hide, but misses the mouseout when the new overlay is shown, so
|
||
|
* the next time it's hidden mouseover events are lost.
|
||
|
*
|
||
|
* Explicitly figure out which item we're hovering over and expand it.
|
||
|
*/
|
||
|
if(this.thumb_container_shown)
|
||
|
{
|
||
|
var element = document.elementFromPoint(this.last_mouse_x, this.last_mouse_y);
|
||
|
element = $(element);
|
||
|
if(element)
|
||
|
{
|
||
|
var li = element.up(".post-thumb");
|
||
|
if(li)
|
||
|
this.expand_post(li.post_idx);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.remove_post = function(right)
|
||
|
{
|
||
|
if(this.posts_populated[0] == this.posts_populated[1])
|
||
|
return false; /* none to remove */
|
||
|
|
||
|
var node = this.container.down(".post-browser-posts");
|
||
|
if(right)
|
||
|
{
|
||
|
--this.posts_populated[1];
|
||
|
var node_to_remove = node.lastChild;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
++this.posts_populated[0];
|
||
|
var node_to_remove = node.firstChild;
|
||
|
}
|
||
|
|
||
|
/* Remove the thumbnail that's no longer visible, and put it in unused_thumb_pool
|
||
|
* so we can reuse it later. This won't grow out of control, since we'll always use
|
||
|
* an item from the pool if available rather than creating a new one. */
|
||
|
var item = node.removeChild(node_to_remove);
|
||
|
this.unused_thumb_pool.push(item);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.remove_all_posts = function()
|
||
|
{
|
||
|
while(this.remove_post(true))
|
||
|
;
|
||
|
}
|
||
|
|
||
|
/* Add the next thumbnail to the left or right side. */
|
||
|
ThumbnailView.prototype.add_post_to_display = function(right)
|
||
|
{
|
||
|
var node = this.container.down(".post-browser-posts");
|
||
|
if(right)
|
||
|
{
|
||
|
var post_idx_to_populate = this.posts_populated[1];
|
||
|
if(post_idx_to_populate == this.post_ids.length)
|
||
|
return false;
|
||
|
++this.posts_populated[1];
|
||
|
|
||
|
var thumb = this.create_thumb(post_idx_to_populate);
|
||
|
node.insertBefore(thumb, null);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(this.posts_populated[0] == 0)
|
||
|
return false;
|
||
|
--this.posts_populated[0];
|
||
|
var post_idx_to_populate = this.posts_populated[0];
|
||
|
var thumb = this.create_thumb(post_idx_to_populate);
|
||
|
node.insertBefore(thumb, node.firstChild);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* Fill the container so post_idx is visible. */
|
||
|
ThumbnailView.prototype.populate_post = function(post_idx)
|
||
|
{
|
||
|
if(this.is_post_idx_shown(post_idx))
|
||
|
return;
|
||
|
|
||
|
/* If post_idx is on the immediate border of what's already displayed, add it incrementally, and
|
||
|
* we'll cull extra posts later. Otherwise, clear all of the posts and populate from scratch. */
|
||
|
if(post_idx == this.posts_populated[1])
|
||
|
{
|
||
|
this.add_post_to_display(true);
|
||
|
return;
|
||
|
}
|
||
|
else if(post_idx == this.posts_populated[0])
|
||
|
{
|
||
|
this.add_post_to_display(false);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* post_idx isn't on the boundary, so we're jumping posts rather than scrolling.
|
||
|
* Clear the container and start over. */
|
||
|
this.remove_all_posts();
|
||
|
|
||
|
var node = this.container.down(".post-browser-posts");
|
||
|
|
||
|
var thumb = this.create_thumb(post_idx);
|
||
|
node.appendChild(thumb);
|
||
|
this.posts_populated[0] = post_idx;
|
||
|
this.posts_populated[1] = post_idx + 1;
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.is_post_idx_shown = function(post_idx)
|
||
|
{
|
||
|
if(post_idx >= this.posts_populated[1])
|
||
|
return false;
|
||
|
return post_idx >= this.posts_populated[0];
|
||
|
}
|
||
|
|
||
|
/* Return the total width of all thumbs to the left or right of post_idx, not
|
||
|
* including itself. */
|
||
|
ThumbnailView.prototype.get_width_adjacent_to_post = function(post_idx, right)
|
||
|
{
|
||
|
var post = $("p" + post_idx);
|
||
|
if(right)
|
||
|
{
|
||
|
var rightmost_node = post.parentNode.lastChild;
|
||
|
if(rightmost_node == post)
|
||
|
return 0;
|
||
|
var right_edge = rightmost_node.offsetLeft + rightmost_node.offsetWidth;
|
||
|
var center_post_right_edge = post.offsetLeft + post.offsetWidth;
|
||
|
return right_edge - center_post_right_edge
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return post.offsetLeft;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Center the thumbnail strip on post_idx. If post_id isn't in the display, do nothing.
|
||
|
* Fire viewer:need-more-thumbs if we're scrolling near the edge of the list. */
|
||
|
ThumbnailView.prototype.center_on_post = function(post_idx)
|
||
|
{
|
||
|
if(!this.post_ids)
|
||
|
{
|
||
|
debug("unexpected: center_on_post has no post_ids");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var post_id = this.post_ids[post_idx];
|
||
|
if(Post.posts.get(post_id) == null)
|
||
|
return;
|
||
|
|
||
|
if(post_idx > this.post_ids.length*3/4)
|
||
|
{
|
||
|
/* We're coming near the end of the loaded posts, so load more. We may be currently
|
||
|
* in the middle of setting up the post; defer this, so we finish what we're doing first. */
|
||
|
(function() {
|
||
|
document.fire("viewer:need-more-thumbs", { view: this });
|
||
|
}).defer();
|
||
|
}
|
||
|
|
||
|
this.centered_post_idx = post_idx;
|
||
|
|
||
|
/* If we're not expanded, we can't figure out how to center it since we'll have no width.
|
||
|
* Also, don't cause thumbnails to be loaded if we're hidden. Just set centered_post_idx,
|
||
|
* and we'll come back here when we're displayed. */
|
||
|
if(!this.thumb_container_shown)
|
||
|
return;
|
||
|
|
||
|
/* If centered_post_offset is high enough to put the actual center post somewhere else,
|
||
|
* adjust it towards zero and change centered_post_idx. This keeps centered_post_idx
|
||
|
* pointing at the item that's actually centered. */
|
||
|
while(1)
|
||
|
{
|
||
|
var post = $("p" + this.centered_post_idx);
|
||
|
if(!post)
|
||
|
break;
|
||
|
var pos = post.offsetWidth/2 + this.centered_post_offset;
|
||
|
if(pos >= 0 && pos < post.offsetWidth)
|
||
|
break;
|
||
|
|
||
|
var next_post_idx = this.centered_post_idx + (this.centered_post_offset > 0? +1:-1);
|
||
|
var next_post = $("p" + next_post_idx);
|
||
|
if(next_post == null)
|
||
|
break;
|
||
|
|
||
|
var current_post_center = post.offsetLeft + post.offsetWidth/2;
|
||
|
var next_post_center = next_post.offsetLeft + next_post.offsetWidth/2;
|
||
|
var distance = next_post_center - current_post_center;
|
||
|
this.centered_post_offset -= distance;
|
||
|
this.centered_post_idx = next_post_idx;
|
||
|
|
||
|
post_idx = this.centered_post_idx;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
this.populate_post(post_idx);
|
||
|
|
||
|
/* Make sure that we have enough posts populated around the one we're centering
|
||
|
* on to fill the display. If we have too many nodes, remove some. */
|
||
|
for(var direction = 0; direction < 2; ++direction)
|
||
|
{
|
||
|
var right = !!direction;
|
||
|
|
||
|
/* We need at least this.container.offsetWidth/2 in each direction. Load a little more, to
|
||
|
* reduce flicker. */
|
||
|
var minimum_distance = this.container.offsetWidth/2;
|
||
|
minimum_distance *= 1.25;
|
||
|
var maximum_distance = minimum_distance + 500;
|
||
|
while(true)
|
||
|
{
|
||
|
var added = false;
|
||
|
var width = this.get_width_adjacent_to_post(post_idx, right);
|
||
|
|
||
|
/* If we're offset to the right then we need more data to the left, and vice versa. */
|
||
|
width += this.centered_post_offset * (right? -1:+1);
|
||
|
if(width < 0)
|
||
|
width = 1;
|
||
|
|
||
|
if(width < minimum_distance)
|
||
|
{
|
||
|
/* We need another post. Stop if there are no more posts to add. */
|
||
|
if(!this.add_post_to_display(right))
|
||
|
break;
|
||
|
added = false;
|
||
|
}
|
||
|
else if(width > maximum_distance)
|
||
|
{
|
||
|
/* We have a lot of posts off-screen. Remove one. */
|
||
|
this.remove_post(right);
|
||
|
|
||
|
/* Sanity check: we should never add and remove in the same direction. If this
|
||
|
* happens, the distance between minimum_distance and maximum_distance may be less
|
||
|
* than the width of a single thumbnail. */
|
||
|
if(added)
|
||
|
{
|
||
|
alert("error");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.preload_thumbs();
|
||
|
|
||
|
/* We always center the thumb. Don't clamp to the edge when we're near the first or last
|
||
|
* item, so we always have empty space on the sides for expanded landscape thumbnails to
|
||
|
* be visible. */
|
||
|
var thumb = $("p" + post_idx);
|
||
|
var center_on_position = this.container.offsetWidth/2;
|
||
|
|
||
|
var shift_pixels_right = center_on_position - thumb.offsetWidth/2 - thumb.offsetLeft;
|
||
|
shift_pixels_right -= this.centered_post_offset;
|
||
|
shift_pixels_right = Math.round(shift_pixels_right);
|
||
|
|
||
|
var node = this.container.down(".post-browser-scroller");
|
||
|
node.setStyle({left: shift_pixels_right + "px"});
|
||
|
}
|
||
|
|
||
|
/* Preload thumbs on the boundary of what's actually displayed. */
|
||
|
ThumbnailView.prototype.preload_thumbs = function()
|
||
|
{
|
||
|
var post_idxs = [];
|
||
|
for(var i = 0; i < 5; ++i)
|
||
|
{
|
||
|
var preload_post_idx = this.posts_populated[0] - i - 1;
|
||
|
if(preload_post_idx >= 0)
|
||
|
post_idxs.push(preload_post_idx);
|
||
|
|
||
|
var preload_post_idx = this.posts_populated[1] + i;
|
||
|
if(preload_post_idx < this.post_ids.length)
|
||
|
post_idxs.push(preload_post_idx);
|
||
|
}
|
||
|
|
||
|
/* Remove any preloaded thumbs that are no longer in the preload list. */
|
||
|
this.thumb_preload_container.get_all().each(function(element) {
|
||
|
var post_idx = element.post_idx;
|
||
|
if(post_idxs.indexOf(post_idx) != -1)
|
||
|
{
|
||
|
/* The post is staying loaded. Clear the value in post_idxs, so we don't load it
|
||
|
* again down below. */
|
||
|
post_idxs[post_idx] = null;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* The post is no longer being preloaded. Remove the preload. */
|
||
|
this.thumb_preload_container.cancel_preload(element);
|
||
|
}.bind(this));
|
||
|
|
||
|
/* Add new preloads. */
|
||
|
for(var i = 0; i < post_idxs.length; ++i)
|
||
|
{
|
||
|
var post_idx = post_idxs[i];
|
||
|
if(post_idx == null)
|
||
|
continue;
|
||
|
|
||
|
var post_id = this.post_ids[post_idx];
|
||
|
var post = Post.posts.get(post_id);
|
||
|
|
||
|
var post_frame = this.post_frames[post_idx];
|
||
|
var url;
|
||
|
if(post_frame != -1)
|
||
|
url = post.frames[post_frame].preview_url;
|
||
|
else
|
||
|
url = post.preview_url;
|
||
|
|
||
|
var element = this.thumb_preload_container.preload(url);
|
||
|
element.post_idx = post_idx;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.expand_post = function(post_idx)
|
||
|
{
|
||
|
/* Thumbs on click for touchpads doesn't make much sense anyway--touching the thumb causes it
|
||
|
* to be loaded. It also triggers a bug in iPhone WebKit (covering up the original target of
|
||
|
* a mouseover during the event seems to cause the subsequent click event to not be delivered).
|
||
|
* Just disable hover thumbnails for touchscreens. */
|
||
|
// if(Prototype.BrowserFeatures.Touchscreen)
|
||
|
/* MI: Using Prototype.BrowserIsMobile instead. */
|
||
|
if (Prototype.BrowserIsMobile)
|
||
|
return;
|
||
|
|
||
|
if(!this.thumb_container_shown)
|
||
|
return;
|
||
|
|
||
|
var post_id = this.post_ids[post_idx];
|
||
|
|
||
|
var overlay = this.container.down(".browser-thumb-hover-overlay");
|
||
|
overlay.hide();
|
||
|
overlay.down("IMG").src = "/images/blank.gif";
|
||
|
|
||
|
this.expanded_post_idx = post_idx;
|
||
|
if(post_idx == null)
|
||
|
return;
|
||
|
|
||
|
var post = Post.posts.get(post_id);
|
||
|
if(post.status == "deleted")
|
||
|
return;
|
||
|
|
||
|
var thumb = $("p" + post_idx);
|
||
|
|
||
|
var bottom = this.container.down(".browser-bottom-bar").offsetHeight;
|
||
|
overlay.style.bottom = bottom + "px";
|
||
|
|
||
|
var post_frame = this.post_frames[post_idx];
|
||
|
var image_width, image_url;
|
||
|
if(post_frame != -1)
|
||
|
{
|
||
|
var frame = post.frames[post_frame];
|
||
|
image_width = frame.preview_width;
|
||
|
image_url = frame.preview_url;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
image_width = post.actual_preview_width;
|
||
|
image_url = post.preview_url;
|
||
|
}
|
||
|
|
||
|
var left = thumb.cumulativeOffset().left - image_width/2 + thumb.offsetWidth/2;
|
||
|
overlay.style.left = left + "px";
|
||
|
|
||
|
/* If the hover thumbnail overflows the right edge of the viewport, it'll extend the document and
|
||
|
* allow scrolling to the right, which we don't want. overflow: hidden doesn't fix this, since this
|
||
|
* element is absolutely positioned. Set the max-width to clip the right side of the thumbnail if
|
||
|
* necessary. */
|
||
|
var max_width = document.viewport.getDimensions().width - left;
|
||
|
overlay.style.maxWidth = max_width + "px";
|
||
|
overlay.href = "/post/browse#" + post.id + this.view.post_frame_hash(post, post_frame);
|
||
|
overlay.down("IMG").src = image_url;
|
||
|
overlay.show();
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.create_thumb = function(post_idx)
|
||
|
{
|
||
|
var post_id = this.post_ids[post_idx];
|
||
|
var post_frame = this.post_frames[post_idx];
|
||
|
|
||
|
var post = Post.posts.get(post_id);
|
||
|
|
||
|
/*
|
||
|
* Reuse thumbnail blocks that are no longer in use, to avoid WebKit memory leaks: it
|
||
|
* doesn't like creating and deleting lots of images (or blocks with images inside them).
|
||
|
*
|
||
|
* Thumbnails are hidden until they're loaded, so we don't show ugly load-borders. This
|
||
|
* also keeps us from showing old thumbnails before the new image is loaded. Use visibility:
|
||
|
* hidden, not display: none, or the size of the image won't be defined, which breaks
|
||
|
* center_on_post.
|
||
|
*/
|
||
|
if(this.unused_thumb_pool.length == 0)
|
||
|
{
|
||
|
var div =
|
||
|
'<div class="inner">' +
|
||
|
'<a class="thumb" tabindex="-1">' +
|
||
|
'<img alt="" class="preview" onload="this.style.visibility = \'visible\';">' +
|
||
|
'</a>' +
|
||
|
'</div>';
|
||
|
var item = $(document.createElement("li"));
|
||
|
item.innerHTML = div;
|
||
|
item.className = "post-thumb";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var item = this.unused_thumb_pool.pop();
|
||
|
}
|
||
|
|
||
|
item.id = "p" + post_idx;
|
||
|
item.post_idx = post_idx;
|
||
|
item.down("A").href = "/post/browse#" + post.id + this.view.post_frame_hash(post, post_frame);
|
||
|
|
||
|
/* If the image is already what we want, then leave it alone. Setting it to what it's
|
||
|
* already set to won't necessarily cause onload to be fired, so it'll never be set
|
||
|
* back to visible. */
|
||
|
var img = item.down("IMG");
|
||
|
var url;
|
||
|
if(post_frame != -1)
|
||
|
url = post.frames[post_frame].preview_url;
|
||
|
else
|
||
|
url = post.preview_url;
|
||
|
if(img.src != url)
|
||
|
{
|
||
|
img.style.visibility = "hidden";
|
||
|
img.src = url;
|
||
|
}
|
||
|
|
||
|
this.set_thumb_dimensions(item);
|
||
|
return item;
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.set_thumb_dimensions = function(li)
|
||
|
{
|
||
|
var post_idx = li.post_idx;
|
||
|
var post_id = this.post_ids[post_idx];
|
||
|
var post_frame = this.post_frames[post_idx];
|
||
|
var post = Post.posts.get(post_id);
|
||
|
|
||
|
var width, height;
|
||
|
if(post_frame != -1)
|
||
|
{
|
||
|
var frame = post.frames[post_frame];
|
||
|
width = frame.preview_width;
|
||
|
height = frame.preview_height;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
width = post.actual_preview_width;
|
||
|
height = post.actual_preview_height;
|
||
|
}
|
||
|
|
||
|
width *= this.config.thumb_scale;
|
||
|
height *= this.config.thumb_scale;
|
||
|
|
||
|
/* This crops blocks that are too wide, but doesn't pad them if they're too
|
||
|
* narrow, since that creates odd spacing.
|
||
|
*
|
||
|
* If the height of this block is changed, adjust .post-browser-posts-container in
|
||
|
* config_changed. */
|
||
|
var block_size = [Math.min(width, 200 * this.config.thumb_scale), 200 * this.config.thumb_scale];
|
||
|
var crop_left = Math.round((width - block_size[0]) / 2);
|
||
|
var pad_top = Math.max(0, block_size[1] - height);
|
||
|
|
||
|
var inner = li.down(".inner");
|
||
|
inner.actual_width = block_size[0];
|
||
|
inner.actual_height = block_size[1];
|
||
|
inner.setStyle({width: block_size[0] + "px", height: block_size[1] + "px"});
|
||
|
|
||
|
var img = inner.down("img");
|
||
|
img.width = width;
|
||
|
img.height = height;
|
||
|
img.setStyle({marginTop: pad_top + "px", marginLeft: -crop_left + "px"});
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.config_changed = function()
|
||
|
{
|
||
|
/* Adjust the size of the container to fit the thumbs at the current scale. They're the
|
||
|
* height of the thumb block, plus ten pixels for padding at the top and bottom. */
|
||
|
var container_height = 200*this.config.thumb_scale + 10;
|
||
|
this.container.down(".post-browser-posts-container").setStyle({height: container_height + "px"});
|
||
|
|
||
|
this.container.select("LI.post-thumb").each(this.set_thumb_dimensions.bind(this));
|
||
|
|
||
|
this.center_on_post_for_scroll(this.centered_post_idx);
|
||
|
}
|
||
|
|
||
|
/* Handle clicks and doubleclicks on thumbnails. These events are handled by
|
||
|
* the container, so we don't need to put event handlers on every thumb. */
|
||
|
ThumbnailView.prototype.container_click_event = function(event)
|
||
|
{
|
||
|
/* Ignore the click if it was stopped by the DragElement. */
|
||
|
if(event.stopped)
|
||
|
return;
|
||
|
|
||
|
if($(event.target).up(".browser-thumb-hover-overlay"))
|
||
|
{
|
||
|
/* The hover overlay was clicked. When the user clicks a thumbnail, this is
|
||
|
* usually what happens, since the hover overlay covers the actual thumbnail. */
|
||
|
this.set_active_post_idx(this.expanded_post_idx);
|
||
|
event.preventDefault();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var li = $(event.target).up(".post-thumb");
|
||
|
if(li == null)
|
||
|
return;
|
||
|
|
||
|
/* An actual thumbnail was clicked. This can happen if we don't have the expanded
|
||
|
* thumbnails for some reason. */
|
||
|
event.preventDefault();
|
||
|
this.set_active_post_idx(li.post_idx);
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.container_dblclick_event = function(event)
|
||
|
{
|
||
|
if(event.button)
|
||
|
return;
|
||
|
|
||
|
event.preventDefault();
|
||
|
this.show_thumb_bar(false);
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.show_thumb_bar = function(shown)
|
||
|
{
|
||
|
if(this.thumb_container_shown == shown)
|
||
|
return;
|
||
|
this.thumb_container_shown = shown;
|
||
|
this.container.show(shown);
|
||
|
|
||
|
/* If the centered post was changed while we were hidden, it wasn't applied by
|
||
|
* center_on_post, so do it now. */
|
||
|
this.center_on_post_for_scroll(this.centered_post_idx);
|
||
|
|
||
|
document.fire("viewer:thumb-bar-changed", {
|
||
|
shown: this.thumb_container_shown,
|
||
|
height: this.thumb_container_shown? this.container.offsetHeight:0
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/* Return the next or previous post, wrapping around if necessary. */
|
||
|
ThumbnailView.prototype.get_adjacent_post_idx_wrapped = function(post_idx, next)
|
||
|
{
|
||
|
post_idx += next? +1:-1;
|
||
|
post_idx = (post_idx + this.post_ids.length) % this.post_ids.length;
|
||
|
return post_idx;
|
||
|
}
|
||
|
|
||
|
ThumbnailView.prototype.displayed_image_loaded_event = function(event)
|
||
|
{
|
||
|
/* If we don't have a loaded search, then we don't have any nearby posts to preload. */
|
||
|
if(this.post_ids == null)
|
||
|
return;
|
||
|
|
||
|
var post_id = event.memo.post_id;
|
||
|
var post_frame = event.memo.post_frame;
|
||
|
var post_idx = this.get_post_idx([post_id, post_frame]);
|
||
|
if(post_idx == null)
|
||
|
return;
|
||
|
|
||
|
/*
|
||
|
* The image in the post we're displaying is finished loading.
|
||
|
*
|
||
|
* Preload the next and previous posts. Normally, one or the other of these will
|
||
|
* already be in cache.
|
||
|
*
|
||
|
* Include the current post in the preloads, so if we switch from a frame back to
|
||
|
* the main image, the frame itself will still be loaded.
|
||
|
*/
|
||
|
var post_ids_to_preload = [];
|
||
|
post_ids_to_preload.push([this.post_ids[post_idx], this.post_frames[post_idx]]);
|
||
|
var adjacent_post_idx = this.get_adjacent_post_idx_wrapped(post_idx, true);
|
||
|
if(adjacent_post_idx != null)
|
||
|
post_ids_to_preload.push([this.post_ids[adjacent_post_idx], this.post_frames[adjacent_post_idx]]);
|
||
|
var adjacent_post_idx = this.get_adjacent_post_idx_wrapped(post_idx, false);
|
||
|
if(adjacent_post_idx != null)
|
||
|
post_ids_to_preload.push([this.post_ids[adjacent_post_idx], this.post_frames[adjacent_post_idx]]);
|
||
|
this.view.preload(post_ids_to_preload);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* This handler handles global keypress bindings, and fires viewer: events. */
|
||
|
function InputHandler()
|
||
|
{
|
||
|
TrackFocus();
|
||
|
|
||
|
/*
|
||
|
* Keypresses are aggrevating:
|
||
|
*
|
||
|
* Opera can only stop key events from keypress, not keydown.
|
||
|
*
|
||
|
* Chrome only sends keydown for non-alpha keys, not keypress.
|
||
|
*
|
||
|
* In Firefox, keypress's keyCode value for non-alpha keys is always 0.
|
||
|
*
|
||
|
* Alpha keys can always be detected with keydown. Don't use keypress; Opera only provides
|
||
|
* charCode to that event, and it's affected by the caps state, which we don't want.
|
||
|
*
|
||
|
* Use OnKey for alpha key bindings. For other keys, use keypress in Opera and FF and
|
||
|
* keydown in other browsers.
|
||
|
*/
|
||
|
var keypress_event_name = window.opera || Prototype.Browser.Gecko? "keypress":"keydown";
|
||
|
document.on(keypress_event_name, this.document_keypress_event.bindAsEventListener(this));
|
||
|
}
|
||
|
|
||
|
InputHandler.prototype.handle_keypress = function(e)
|
||
|
{
|
||
|
var key = e.charCode;
|
||
|
if(!key)
|
||
|
key = e.keyCode; /* Opera */
|
||
|
if(key == Event.KEY_ESC)
|
||
|
{
|
||
|
if(document.focusedElement && document.focusedElement.blur && !document.focusedElement.hasClassName("no-blur-on-escape"))
|
||
|
{
|
||
|
document.focusedElement.blur();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var target = e.target;
|
||
|
if(target.tagName == "INPUT" || target.tagName == "TEXTAREA")
|
||
|
return false;
|
||
|
|
||
|
if(key == 63) // ?, f
|
||
|
{
|
||
|
debug("xxx");
|
||
|
document.fire("viewer:show-help");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey)
|
||
|
return false;
|
||
|
var grave_keycode = Prototype.Browser.WebKit? 192: 96;
|
||
|
if(key == 32) // space
|
||
|
document.fire("viewer:set-thumb-bar", { toggle: true });
|
||
|
else if(key == 49) // 1
|
||
|
document.fire("viewer:vote", { score: 1 });
|
||
|
else if(key == 50) // 2
|
||
|
document.fire("viewer:vote", { score: 2 });
|
||
|
else if(key == 51) // 3
|
||
|
document.fire("viewer:vote", { score: 3 });
|
||
|
else if(key == grave_keycode) // `
|
||
|
document.fire("viewer:vote", { score: 0 });
|
||
|
else if(key == 65 || key == 97) // A, b
|
||
|
document.fire("viewer:show-next-post", { prev: true });
|
||
|
else if(key == 69 || key == 101) // E, e
|
||
|
document.fire("viewer:edit-post");
|
||
|
else if(key == 83 || key == 115) // S, s
|
||
|
document.fire("viewer:show-next-post", { prev: false });
|
||
|
else if(key == 70 || key == 102) // F, f
|
||
|
document.fire("viewer:focus-tag-box");
|
||
|
else if(key == 86 || key == 118) // V, v
|
||
|
document.fire("viewer:view-large-toggle");
|
||
|
else if(key == Event.KEY_PAGEUP)
|
||
|
document.fire("viewer:show-next-post", { prev: true });
|
||
|
else if(key == Event.KEY_PAGEDOWN)
|
||
|
document.fire("viewer:show-next-post", { prev: false });
|
||
|
else if(key == Event.KEY_LEFT)
|
||
|
document.fire("viewer:scroll", { left: true });
|
||
|
else if(key == Event.KEY_RIGHT)
|
||
|
document.fire("viewer:scroll", { left: false });
|
||
|
else
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
InputHandler.prototype.document_keypress_event = function(e)
|
||
|
{
|
||
|
//alert(e.charCode + ", " + e.keyCode);
|
||
|
if(this.handle_keypress(e))
|
||
|
e.stop();
|
||
|
}
|
||
|
|