1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-20 17:28:51 +02:00
phorge-phorge/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js
epriestley 942f504893 Remove inline gutter in Pholio
Summary: This greatly simplifies inline comments while retaining their functionality. This is probably not where we want to end up, but will let us figure out what we're doing with the stage without worrying about inlines.

Test Plan: See screenshots.

Reviewers: chad

Reviewed By: chad

Subscribers: epriestley

Differential Revision: https://secure.phabricator.com/D9537
2014-06-14 18:41:45 -07:00

710 lines
17 KiB
JavaScript

/**
* @provides javelin-behavior-pholio-mock-view
* @requires javelin-behavior
* javelin-util
* javelin-stratcom
* javelin-dom
* javelin-vector
* javelin-magical-init
* javelin-request
* javelin-history
* javelin-workflow
* javelin-mask
* javelin-behavior-device
* phabricator-keyboard-shortcut
*/
JX.behavior('pholio-mock-view', function(config) {
var is_dragging = false;
var drag_begin;
var drag_end;
var panel = JX.$(config.panelID);
var viewport = JX.$(config.viewportID);
var selection_reticle;
var active_image;
var inline_comments = {};
/* -( Stage )-------------------------------------------------------------- */
var stage = (function() {
var loading = false;
var stageElement = JX.$(config.panelID);
var viewElement = JX.$(config.viewportID);
var reticles = [];
function begin_load() {
if (loading) {
return;
}
loading = true;
clear_stage();
draw_loading();
}
function end_load() {
if (!loading) {
return;
}
loading = false;
draw_loading();
}
function draw_loading() {
JX.DOM.alterClass(stageElement, 'pholio-image-loading', loading);
}
function add_reticle(reticle, id) {
mark_ref(reticle, id);
reticles.push(reticle);
viewElement.appendChild(reticle);
}
function clear_stage() {
var ii;
for (ii = 0; ii < reticles.length; ii++) {
JX.DOM.remove(reticles[ii]);
}
reticles = [];
}
function mark_ref(node, id) {
JX.Stratcom.addSigil(node, 'pholio-inline-ref');
JX.Stratcom.addData(node, {inlineID: id});
}
return {
beginLoad: begin_load,
endLoad: end_load,
addReticle: add_reticle,
clearStage: clear_stage
};
})();
JX.enableDispatch(document.body, 'mouseenter');
JX.enableDispatch(document.body, 'mouseleave');
JX.Stratcom.listen(
['mouseenter', 'mouseover'],
'mock-panel',
function(e) {
JX.DOM.alterClass(e.getNode('mock-panel'), 'mock-has-cursor', true);
});
JX.Stratcom.listen('mouseleave', 'mock-panel', function(e) {
var node = e.getNode('mock-panel');
if (e.getTarget() == node) {
JX.DOM.alterClass(node, 'mock-has-cursor', false);
}
});
function get_image_index(id) {
for (var ii = 0; ii < config.images.length; ii++) {
if (config.images[ii].id == id) {
return ii;
}
}
return null;
}
function get_image(id) {
var idx = get_image_index(id);
if (idx === null) {
return idx;
}
return config.images[idx];
}
function onload_image(id) {
if (active_image.id != id) {
// The user has clicked another image before this one loaded, so just
// bail.
return;
}
active_image.tag = this;
redraw_image();
}
function switch_image(delta) {
if (!active_image) {
return;
}
var idx = get_image_index(active_image.id);
idx = (idx + delta + config.images.length) % config.images.length;
select_image(config.images[idx].id);
}
function redraw_image() {
// Force the stage to scale as a function of the viewport size. Broadly,
// we take the full viewport and subtract 12px top and bottom.
var new_y = (JX.Vector.getViewport().y - 24) ;
new_y = Math.max(320, new_y);
panel.style.height = new_y + 'px';
if (!active_image || !active_image.tag) {
return;
}
var tag = active_image.tag;
// If the image is too wide or tall for the viewport, scale it down so it
// fits.
var w = JX.Vector.getDim(panel);
w.x -= 24;
w.y -= 24;
var scale = 1;
if (w.x < tag.naturalWidth) {
scale = Math.min(scale, w.x / tag.naturalWidth);
}
if (w.y < tag.naturalHeight) {
scale = Math.min(scale, w.y / tag.naturalHeight);
}
if (scale < 1) {
tag.width = Math.floor(scale * tag.naturalWidth);
tag.height = Math.floor(scale * tag.naturalHeight);
} else {
tag.width = tag.naturalWidth;
tag.height = tag.naturalHeight;
}
viewport.style.top = Math.floor((new_y - tag.height) / 2) + 'px';
stage.endLoad();
JX.DOM.setContent(viewport, tag);
redraw_inlines(active_image.id);
}
function select_image(image_id) {
active_image = get_image(image_id);
active_image.tag = null;
stage.beginLoad();
var img = JX.$N('img', {className: 'pholio-mock-image'});
img.onload = JX.bind(img, onload_image, active_image.id);
img.src = active_image.fullURI;
var thumbs = JX.DOM.scry(
JX.$('pholio-mock-carousel'),
'a',
'mock-thumbnail');
for(var k in thumbs) {
var thumb_meta = JX.Stratcom.getData(thumbs[k]);
JX.DOM.alterClass(
thumbs[k],
'pholio-mock-carousel-thumb-current',
(active_image.id == thumb_meta.imageID));
}
load_inline_comments();
if (image_id != config.selectedID) {
JX.History.replace(active_image.pageURI);
}
}
JX.Stratcom.listen(
['mousedown', 'click'],
'mock-thumbnail',
function(e) {
if (!e.isNormalMouseEvent()) {
return;
}
e.kill();
select_image(e.getNodeData('mock-thumbnail').imageID);
});
select_image(config.selectedID);
JX.Stratcom.listen('mousedown', 'mock-viewport', function(e) {
if (!e.isNormalMouseEvent()) {
return;
}
if (JX.Device.getDevice() != 'desktop') {
return;
}
if (config.viewMode == 'history') {
return;
}
if (JX.Stratcom.pass()) {
return;
}
if (is_dragging) {
return;
}
e.kill();
is_dragging = true;
drag_begin = get_image_xy(JX.$V(e));
drag_end = drag_begin;
redraw_selection();
});
JX.enableDispatch(document.body, 'mousemove');
JX.Stratcom.listen('mousemove', null, function(e) {
if (!is_dragging) {
return;
}
drag_end = get_image_xy(JX.$V(e));
redraw_selection();
});
JX.Stratcom.listen(
'mousedown',
'pholio-inline-ref',
function(e) {
e.kill();
var id = e.getNodeData('pholio-inline-ref').inlineID;
var active_id = active_image.id;
var handler = function(r) {
var inlines = inline_comments[active_id];
for (var ii = 0; ii < inlines.length; ii++) {
if (inlines[ii].id == id) {
if (r.id) {
inlines[ii] = r;
} else {
inlines.splice(ii, 1);
}
break;
}
}
redraw_inlines(active_id);
JX.DOM.invoke(JX.$(config.commentFormID), 'shouldRefresh');
};
new JX.Workflow('/pholio/inline/' + id + '/')
.setHandler(handler)
.start();
});
JX.Stratcom.listen(
'mouseup',
null,
function(e) {
if (!is_dragging) {
return;
}
is_dragging = false;
if (!config.loggedIn) {
new JX.Workflow(config.logInLink).start();
return;
}
drag_end = get_image_xy(JX.$V(e));
var scale = get_image_scale();
resize_selection(16);
var data = {
mockID: config.mockID,
imageID: active_image.id,
startX: Math.min(drag_begin.x, drag_end.x),
startY: Math.min(drag_begin.y, drag_end.y),
endX: Math.max(drag_begin.x, drag_end.x),
endY: Math.max(drag_begin.y, drag_end.y)
};
var handler = function(r) {
if (!inline_comments[active_image.id]) {
inline_comments[active_image.id] = [];
}
inline_comments[active_image.id].push(r);
redraw_inlines(active_image.id);
JX.DOM.invoke(JX.$(config.commentFormID), 'shouldRefresh');
};
clear_selection();
new JX.Workflow('/pholio/inline/', data)
.setHandler(handler)
.start();
});
function resize_selection(min_size) {
var start = {
x: Math.min(drag_begin.x, drag_end.x),
y: Math.min(drag_begin.y, drag_end.y)
};
var end = {
x: Math.max(drag_begin.x, drag_end.x),
y: Math.max(drag_begin.y, drag_end.y)
};
var width = end.x - start.x;
var height = end.y - start.y;
var addon;
if (width < min_size) {
addon = (min_size-width)/2;
start.x = Math.max(0, start.x - addon);
end.x = Math.min(active_image.tag.naturalWidth, end.x + addon);
if (start.x === 0) {
end.x = Math.min(min_size, active_image.tag.naturalWidth);
} else if (end.x == active_image.tag.naturalWidth) {
start.x = Math.max(0, active_image.tag.naturalWidth - min_size);
}
}
if (height < min_size) {
addon = (min_size-height)/2;
start.y = Math.max(0, start.y - addon);
end.y = Math.min(active_image.tag.naturalHeight, end.y + addon);
if (start.y === 0) {
end.y = Math.min(min_size, active_image.tag.naturalHeight);
} else if (end.y == active_image.tag.naturalHeight) {
start.y = Math.max(0, active_image.tag.naturalHeight - min_size);
}
}
drag_begin = start;
drag_end = end;
redraw_selection();
}
function redraw_inlines(id) {
if (!active_image) {
return;
}
if (active_image.id != id) {
return;
}
stage.clearStage();
var comment_holder = JX.$('mock-inline-comments');
JX.DOM.setContent(comment_holder, render_image_info(active_image));
var inlines = inline_comments[active_image.id];
if (!inlines || !inlines.length) {
return;
}
for (var ii = 0; ii < inlines.length; ii++) {
var inline = inlines[ii];
if (!active_image.tag) {
// The image itself hasn't loaded yet, so we can't draw the inline
// reticles.
continue;
}
var classes = [];
if (!inline.transactionPHID) {
classes.push('pholio-mock-reticle-draft');
} else {
classes.push('pholio-mock-reticle-final');
}
var inline_selection = render_reticle(classes);
stage.addReticle(inline_selection, inline.id);
position_inline_rectangle(inline, inline_selection);
}
}
function position_inline_rectangle(inline, rect) {
var scale = get_image_scale();
JX.$V(scale * inline.x, scale * inline.y).setPos(rect);
JX.$V(scale * inline.width, scale * inline.height).setDim(rect);
}
function get_image_xy(p) {
var img = active_image.tag;
var imgp = JX.$V(img);
var scale = 1 / get_image_scale();
var x = scale * Math.max(0, Math.min(p.x - imgp.x, img.width));
var y = scale * Math.max(0, Math.min(p.y - imgp.y, img.height));
return {
x: x,
y: y
};
}
function get_image_scale() {
var img = active_image.tag;
return Math.min(
img.width / img.naturalWidth,
img.height / img.naturalHeight);
}
function redraw_selection() {
var classes = ['pholio-mock-reticle-selection'];
selection_reticle = selection_reticle || render_reticle(classes);
var p = JX.$V(
Math.min(drag_begin.x, drag_end.x),
Math.min(drag_begin.y, drag_end.y));
var d = JX.$V(
Math.max(drag_begin.x, drag_end.x) - p.x,
Math.max(drag_begin.y, drag_end.y) - p.y);
var scale = get_image_scale();
p.x *= scale;
p.y *= scale;
d.x *= scale;
d.y *= scale;
viewport.appendChild(selection_reticle);
p.setPos(selection_reticle);
d.setDim(selection_reticle);
}
function clear_selection() {
selection_reticle && JX.DOM.remove(selection_reticle);
selection_reticle = null;
}
function load_inline_comments() {
var id = active_image.id;
var inline_comments_uri = '/pholio/inline/list/' + id + '/';
new JX.Request(inline_comments_uri, function(r) {
inline_comments[id] = r;
redraw_inlines(id);
}).send();
}
load_inline_comments();
if (config.loggedIn && config.commentFormID) {
JX.DOM.invoke(JX.$(config.commentFormID), 'shouldRefresh');
}
JX.Stratcom.listen('resize', null, redraw_image);
redraw_image();
/* -( Keyboard Shortcuts )------------------------------------------------- */
new JX.KeyboardShortcut(['j', 'right'], 'Show next image.')
.setHandler(function() {
switch_image(1);
})
.register();
new JX.KeyboardShortcut(['k', 'left'], 'Show previous image.')
.setHandler(function() {
switch_image(-1);
})
.register();
JX.DOM.listen(panel, 'gesture.swipe.end', null, function(e) {
var data = e.getData();
if (data.length <= (JX.Vector.getDim(panel) / 2)) {
// If the user didn't move their finger far enough, don't switch.
return;
}
switch_image(data.direction == 'right' ? -1 : 1);
});
/* -( Render )------------------------------------------------------------- */
function render_image_info(image) {
var info = [];
var title = JX.$N(
'div',
{className: 'pholio-image-title'},
image.title);
info.push(title);
var desc = JX.$N(
'div',
{className: 'pholio-image-description'},
image.desc);
info.push(desc);
if (!image.isObsolete) {
var embed = JX.$N(
'div',
{className: 'pholio-image-embedding'},
JX.$H('Embed this image:<br />{M' + config.mockID +
', image=' + image.id + '}'));
info.push(embed);
}
// Render image dimensions and visible size. If we have this infomation
// from the server we can display some of it immediately; otherwise, we need
// to wait for the image to load so we can read dimension information from
// it.
var image_x = image.width;
var image_y = image.height;
var display_x = null;
if (image.tag) {
image_x = image.tag.naturalWidth;
image_y = image.tag.naturalHeight;
display_x = image.tag.width;
}
var visible = [];
if (image_x) {
visible.push([image_x, '\u00d7', image_y, 'px']);
if (display_x) {
var area = Math.round(100 * (display_x / image_x));
visible.push(' ');
visible.push(
JX.$N(
'span',
{className: 'pholio-visible-size'},
['(', area, '%', ')']));
}
}
if (visible.length) {
info.push(visible);
}
var full_link = JX.$N(
'a',
{href: image.fullURI, target: '_blank'},
'View Full Image');
info.push(full_link);
if (config.viewMode != 'history') {
var history_link = JX.$N(
'a',
{ href: image.historyURI },
'View Image History');
info.push(history_link);
}
for (var ii = 0; ii < info.length; ii++) {
info[ii] = JX.$N('div', {className: 'pholio-image-info-item'}, info[ii]);
}
info = JX.$N('div', {className: 'pholio-image-info'}, info);
return info;
}
function render_reticle(classes) {
return JX.$N(
'div',
{className: ['pholio-mock-reticle'].concat(classes).join(' ')});
}
/* -( Device Lightbox )---------------------------------------------------- */
// On devices, we show images full-size when the user taps them instead of
// attempting to implement inlines.
var lightbox = null;
JX.Stratcom.listen('click', 'mock-viewport', function(e) {
if (!e.isNormalMouseEvent()) {
return;
}
if (JX.Device.getDevice() == 'desktop') {
return;
}
lightbox_attach();
e.kill();
});
JX.Stratcom.listen('click', 'pholio-device-lightbox', lightbox_detach);
JX.Stratcom.listen('resize', null, lightbox_resize);
function lightbox_attach() {
JX.DOM.alterClass(document.body, 'lightbox-attached', true);
JX.Mask.show('jx-dark-mask');
lightbox = lightbox_render();
var image = JX.$N('img');
image.onload = lightbox_loaded;
setTimeout(function() {
image.src = active_image.fullURI;
}, 1000);
JX.DOM.setContent(lightbox, image);
JX.DOM.alterClass(lightbox, 'pholio-device-lightbox-loading', true);
lightbox_resize();
document.body.appendChild(lightbox);
}
function lightbox_detach() {
JX.DOM.remove(lightbox);
JX.Mask.hide();
JX.DOM.alterClass(document.body, 'lightbox-attached', false);
lightbox = null;
}
function lightbox_resize(e) {
if (!lightbox) {
return;
}
JX.Vector.getScroll().setPos(lightbox);
JX.Vector.getViewport().setDim(lightbox);
}
function lightbox_loaded() {
JX.DOM.alterClass(lightbox, 'pholio-device-lightbox-loading', false);
}
function lightbox_render() {
var el = JX.$N('div', {className: 'pholio-device-lightbox'});
JX.Stratcom.addSigil(el, 'pholio-device-lightbox');
return el;
}
/* -( Preload )------------------------------------------------------------ */
var preload = [];
for (var ii = 0; ii < config.images.length; ii++) {
preload.push(config.images[ii].fullURI);
}
function preload_next() {
next_src = preload[0];
if (!next_src) {
return;
}
preload.splice(0, 1);
var img = JX.$N('img');
img.onload = preload_next;
img.onerror = preload_next;
img.src = next_src;
}
preload_next();
});