hotkeys: Add lightbox image feed with controls.

This adds an image feed that you can scroll through with hotkeys
in the lightbox.

The left and right arrow keys along with the left and right arrows
will go to the prev/next image, and clicking on an image will also
take a user to that image.
This commit is contained in:
Brock Whittaker
2017-03-18 17:51:20 -07:00
committed by Tim Abbott
parent fa5a093738
commit 2775707a67
8 changed files with 190 additions and 4 deletions

View File

@@ -215,6 +215,7 @@ function stubbing(func_name_to_stub, test_function) {
end: 35, end: 35,
home: 36, home: 36,
left_arrow: 37, left_arrow: 37,
right_arrow: 39,
page_up: 33, page_up: 33,
page_down: 34, page_down: 34,
spacebar: 32, spacebar: 32,
@@ -273,6 +274,10 @@ function stubbing(func_name_to_stub, test_function) {
assert_mapping('up_arrow', 'navigate.up'); assert_mapping('up_arrow', 'navigate.up');
assert_mapping('+', 'reactions.toggle_reaction', true, false); assert_mapping('+', 'reactions.toggle_reaction', true, false);
hotkey.is_lightbox_open = return_true;
assert_mapping('left_arrow', 'lightbox.prev');
assert_mapping('right_arrow', 'lightbox.next');
hotkey.is_settings_page = return_true; hotkey.is_settings_page = return_true;
assert_unmapped('end'); assert_unmapped('end');
assert_unmapped('home'); assert_unmapped('home');

View File

@@ -526,6 +526,48 @@ $(function () {
$("#lightbox_overlay .download").click(function () { $("#lightbox_overlay .download").click(function () {
this.blur(); this.blur();
}); });
$("#lightbox_overlay").on("click", ".image-list .image", function () {
var $image_list = $(this).parent();
var image = new Image();
image.src = this.dataset.src;
image.title = this.title;
lightbox.open({
type: "photo",
user: message_store.get($(this).attr("data-zid")).sender_full_name,
image: image,
});
$(".image-list .image.selected").removeClass("selected");
$(this).addClass("selected");
var parentOffset = this.parentNode.clientWidth + this.parentNode.scrollLeft;
// this is the left and right of the image compared to its parent.
var coords = {
left: this.offsetLeft,
right: this.offsetLeft + this.clientWidth,
};
if (coords.right > parentOffset) {
// add 2px margin
$image_list.animate({
scrollLeft: coords.right - this.parentNode.clientWidth + 2,
}, 100);
} else if (coords.left < this.parentNode.scrollLeft) {
// subtract 2px margin
$image_list.animate({ scrollLeft: coords.left - 2 }, 100);
}
});
$("#lightbox_overlay").on("click", ".center .arrow", function () {
var direction = $(this).attr("data-direction");
if (/^(next|prev)$/.test(direction)) {
lightbox[direction]();
}
});
}()); }());
// MAIN CLICK HANDLER // MAIN CLICK HANDLER

View File

@@ -29,6 +29,10 @@ exports.is_settings_page = function () {
return (/^#*(settings|administration)/g).test(window.location.hash); return (/^#*(settings|administration)/g).test(window.location.hash);
}; };
exports.is_lightbox_open = function () {
return lightbox.is_open;
};
var actions_dropdown_hotkeys = [ var actions_dropdown_hotkeys = [
'down_arrow', 'down_arrow',
'up_arrow', 'up_arrow',
@@ -57,7 +61,8 @@ var keydown_unshift_mappings = {
34: {name: 'page_down', message_view_only: true}, // page down 34: {name: 'page_down', message_view_only: true}, // page down
35: {name: 'end', message_view_only: true}, // end 35: {name: 'end', message_view_only: true}, // end
36: {name: 'home', message_view_only: true}, // home 36: {name: 'home', message_view_only: true}, // home
37: {name: 'left_arrow', message_view_only: true}, // left arrow 37: {name: 'left_arrow', message_view_only: false}, // left arrow
39: {name: 'right_arrow', message_view_only: false}, // right arrow
38: {name: 'up_arrow', message_view_only: true}, // up arrow 38: {name: 'up_arrow', message_view_only: true}, // up arrow
40: {name: 'down_arrow', message_view_only: true}, // down arrow 40: {name: 'down_arrow', message_view_only: true}, // down arrow
}; };
@@ -183,7 +188,7 @@ exports.process_escape_key = function (e) {
return false; return false;
} }
if ($("#lightbox_overlay").hasClass("show")) { if (exports.is_lightbox_open()) {
modals.close_modal("lightbox"); modals.close_modal("lightbox");
return true; return true;
} }
@@ -506,10 +511,22 @@ exports.process_hotkey = function (e, hotkey) {
} }
if (event_name === 'left_arrow') { if (event_name === 'left_arrow') {
if (exports.is_lightbox_open()) {
lightbox.prev();
return true;
}
message_edit.edit_last_sent_message(); message_edit.edit_last_sent_message();
return true; return true;
} }
if (event_name === 'right_arrow') {
if (exports.is_lightbox_open()) {
lightbox.next();
return true;
}
}
// Shortcuts that don't require a message // Shortcuts that don't require a message
switch (event_name) { switch (event_name) {
case 'compose': // 'c': compose case 'compose': // 'c': compose

View File

@@ -1,10 +1,40 @@
var lightbox = (function () { var lightbox = (function () {
var exports = {}; var exports = {};
var images = [];
var is_open = false;
var get_image_title = function (image) {
var image_title = $(image).attr("title");
if (image_title) {
return image_title;
}
return $(image).parent().attr("title");
};
function display_image(image, user) { function display_image(image, user) {
if (!is_open) {
images = Array.prototype.slice.call($(".focused_table .messagebox-content img"));
var $image_list = $("#lightbox_overlay .image-list").html("");
images.forEach(function (img) {
var src = img.getAttribute("src");
var className = $(image).attr("src").match(src) ? "image selected" : "image";
var node = $("<div></div>", {
class: className,
title: get_image_title(img),
"data-zid": $(img).closest(".message_row").attr("zid"),
"data-src": src,
}).css({ backgroundImage: "url(" + src + ")"});
$image_list.append(node);
}, "");
}
// image should be an Image Object in JavaScript. // image should be an Image Object in JavaScript.
var url = $(image).attr("src"); var url = $(image).attr("src");
var title = $(image).parent().attr("title"); var title = get_image_title(image);
$("#lightbox_overlay .player-container").hide(); $("#lightbox_overlay .player-container").hide();
$("#lightbox_overlay .image-actions, .image-description, .download").show(); $("#lightbox_overlay .image-actions, .image-description, .download").show();
@@ -38,9 +68,11 @@ exports.open = function (data) {
switch (data.type) { switch (data.type) {
case "photo": case "photo":
display_image(data.image, data.user); display_image(data.image, data.user);
is_open = true;
break; break;
case "youtube": case "youtube":
display_youtube_video(data.id); display_youtube_video(data.id);
is_open = true;
break; break;
default: default:
break; break;
@@ -48,6 +80,7 @@ exports.open = function (data) {
$("#lightbox_overlay").addClass("show"); $("#lightbox_overlay").addClass("show");
popovers.hide_all(); popovers.hide_all();
lightbox.is_open = true;
}; };
exports.show_from_selected_message = function () { exports.show_from_selected_message = function () {
@@ -74,6 +107,25 @@ exports.show_from_inline_image = function ($img) {
} }
}; };
exports.prev = function () {
$(".image-list .image.selected").prev().click();
};
exports.next = function () {
$(".image-list .image.selected").next().click();
};
Object.defineProperty(exports, "is_open", {
get: function () {
return is_open;
},
set: function (value) {
if (typeof value === "boolean") {
is_open = value;
}
},
});
return exports; return exports;
}()); }());

View File

@@ -21,6 +21,7 @@ var modals = (function () {
lightbox: function () { lightbox: function () {
$(".player-container iframe").remove(); $(".player-container iframe").remove();
lightbox.is_open = false;
document.activeElement.blur(); document.activeElement.blur();
}, },

View File

@@ -11,7 +11,7 @@
justify-content: center; justify-content: center;
position: relative; position: relative;
width: 100%; width: 100%;
height: calc(100% - 65px - 30px); height: calc(100% - 65px - 90px);
margin: 0px; margin: 0px;
background-size: contain; background-size: contain;
@@ -157,6 +157,58 @@
white-space: pre; white-space: pre;
} }
#lightbox_overlay .center .arrow {
display: inline-block;
vertical-align: top;
margin-top: 25px;
padding: 5px 10px;
color: #fff;
font-size: 1.8em;
font-weight: 100;
transform: scaleY(2);
cursor: pointer;
opacity: 0.5;
transition: all 0.3s ease;
}
#lightbox_overlay .center .arrow:hover {
opacity: 1;
}
#lightbox_overlay .center .image-list {
position: relative;
display: inline-block;
padding: 15px 0px 12px 0px;
height: 50px;
font-size: 0px;
max-width: 40vw;
overflow-x: auto;
white-space: nowrap;
}
#lightbox_overlay .center .image-list .image {
display: inline-block;
vertical-align: top;
width: 50px;
height: 50px;
margin: 0px 2px;
background-color: rgba(240, 240, 240, 0.2);
opacity: 0.5;
background-size: cover;
background-position: center;
cursor: pointer;
}
#lightbox_overlay .image-list .image.selected {
opacity: 1;
}
.image-actions .button { .image-actions .button {
font-size: 0.8rem; font-size: 0.8rem;
min-width: inherit; min-width: inherit;
@@ -199,4 +251,8 @@
#lightbox_overlay .image-description .title { #lightbox_overlay .image-description .title {
max-width: calc(100% - 60px); max-width: calc(100% - 60px);
} }
#lightbox_overlay .center .image-list {
max-width: 80vw;
}
} }

View File

@@ -41,6 +41,14 @@ a {
cursor: pointer; cursor: pointer;
} }
.no-select {
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
p.n-margin { p.n-margin {
margin: 10px 0px 0px 0px; margin: 10px 0px 0px 0px;
} }

View File

@@ -18,4 +18,9 @@
<div class="image-preview overlay-content"></div> <div class="image-preview overlay-content"></div>
<div class="player-container"></div> <div class="player-container"></div>
<div class="center">
<div class="arrow no-select" data-direction="prev">&lt;</div>
<div class="image-list"></div>
<div class="arrow no-select" data-direction="next">&gt;</div>
</div>
</div> </div>