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,
home: 36,
left_arrow: 37,
right_arrow: 39,
page_up: 33,
page_down: 34,
spacebar: 32,
@ -273,6 +274,10 @@ function stubbing(func_name_to_stub, test_function) {
assert_mapping('up_arrow', 'navigate.up');
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;
assert_unmapped('end');
assert_unmapped('home');

View File

@ -526,6 +526,48 @@ $(function () {
$("#lightbox_overlay .download").click(function () {
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

View File

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

View File

@ -1,10 +1,40 @@
var lightbox = (function () {
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) {
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.
var url = $(image).attr("src");
var title = $(image).parent().attr("title");
var title = get_image_title(image);
$("#lightbox_overlay .player-container").hide();
$("#lightbox_overlay .image-actions, .image-description, .download").show();
@ -38,9 +68,11 @@ exports.open = function (data) {
switch (data.type) {
case "photo":
display_image(data.image, data.user);
is_open = true;
break;
case "youtube":
display_youtube_video(data.id);
is_open = true;
break;
default:
break;
@ -48,6 +80,7 @@ exports.open = function (data) {
$("#lightbox_overlay").addClass("show");
popovers.hide_all();
lightbox.is_open = true;
};
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;
}());

View File

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

View File

@ -11,7 +11,7 @@
justify-content: center;
position: relative;
width: 100%;
height: calc(100% - 65px - 30px);
height: calc(100% - 65px - 90px);
margin: 0px;
background-size: contain;
@ -157,6 +157,58 @@
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 {
font-size: 0.8rem;
min-width: inherit;
@ -199,4 +251,8 @@
#lightbox_overlay .image-description .title {
max-width: calc(100% - 60px);
}
#lightbox_overlay .center .image-list {
max-width: 80vw;
}
}

View File

@ -41,6 +41,14 @@ a {
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 {
margin: 10px 0px 0px 0px;
}

View File

@ -18,4 +18,9 @@
<div class="image-preview overlay-content"></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>