phorge/webroot/rsrc/js/core/ToolTip.js
epriestley af07600aaa Make Differential objective markers show a brighter "editing" state
Summary:
Ref T12733.

  - While editing a comment, show a pink star ({icon star, color=pink}) with a tooltip.
  - Slight UI tweaks, including draft comments getting an indigo pencil ({icon pencil, color=indigo}).

Test Plan: {F4968470}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T12733

Differential Revision: https://secure.phabricator.com/D17977
2017-05-20 07:57:38 -07:00

243 lines
6.1 KiB
JavaScript

/**
* @requires javelin-install
* javelin-util
* javelin-dom
* javelin-vector
* @provides phabricator-tooltip
* @javelin
*/
JX.install('Tooltip', {
statics: {
_node: null,
_last: null,
_lock: 0,
show : function(root, scale, align, content) {
var self = JX.Tooltip;
if (self._lock) {
return;
}
if (content === null) {
return;
}
if (__DEV__) {
switch (align) {
case 'N':
case 'E':
case 'S':
case 'W':
break;
default:
JX.$E(
'Only alignments "N" (north), "E" (east), "S" (south), ' +
'and "W" (west) are supported.'
);
break;
}
}
var node_inner = JX.$N(
'div',
{ className: 'jx-tooltip-inner' },
[
JX.$N('div', { className: 'jx-tooltip' }, content),
JX.$N('div', { className: 'jx-tooltip-anchor' })
]);
var node = JX.$N(
'div',
{ className: 'jx-tooltip-container' },
node_inner);
if (scale == 'auto') {
node.style.maxWidth = '';
} else {
node.style.maxWidth = scale + 'px';
}
JX.Tooltip.hide();
self._node = node;
// Append the tip to the document, but offscreen, so we can measure it.
node.style.left = '-10000px';
document.body.appendChild(node);
// Jump through some hoops trying to auto-position the tooltip
var pos = self._getSmartPosition(align, root, node);
pos.setPos(node);
// Animate the tip if we haven't shown any tips recently. If we are
// already showing a tip (or hid one very recently) just show the tip
// immediately. This makes hunting for a particular item by mousing
// through tips smoother: you only have to sit through the animation
// once, at the beginning.
var is_recent = false;
var last_tip = self._last;
if (last_tip) {
// If we recently hid a tip, compute how many milliseconds ago we
// hid it.
var last_tip_age = (new Date().getTime() - self._last);
if (last_tip_age < 500) {
is_recent = true;
}
}
if (!is_recent) {
JX.DOM.alterClass(node, 'jx-tooltip-appear', true);
}
},
_getSmartPosition: function (align, root, node) {
var self = JX.Tooltip;
// Figure out how to position the tooltip on screen. We will try the
// configured aligment first.
var try_alignments = [align];
// If the configured alignment does not fit, we'll try the opposite
// alignment.
var opposites = {
N: 'S',
S: 'N',
E: 'W',
W: 'E'
};
try_alignments.push(opposites[align]);
// Then we'll try the other alignments, in arbitrary order.
for (var k in opposites) {
try_alignments.push(k);
}
var use_alignment = null;
var use_pos = null;
for (var ii = 0; ii < try_alignments.length; ii++) {
var try_alignment = try_alignments[ii];
var pos = self._proposePosition(try_alignment, root, node);
if (self.isOnScreen(pos, node)) {
use_alignment = try_alignment;
use_pos = pos;
break;
}
}
// If we don't come up with a good answer, default to the configured
// alignment.
if (use_alignment === null) {
use_alignment = align;
use_pos = self._proposePosition(use_alignment, root, node);
}
self._setAnchor(use_alignment);
return pos;
},
_proposePosition: function (align, root, node) {
var p = JX.$V(root);
var d = JX.Vector.getDim(root);
var n = JX.Vector.getDim(node);
var l = 0;
var t = 0;
// Caculate the tip so it's nicely aligned.
switch (align) {
case 'N':
l = parseInt(p.x - ((n.x - d.x) / 2), 10);
t = parseInt(p.y - n.y, 10);
break;
case 'E':
l = parseInt(p.x + d.x, 10);
t = parseInt(p.y - ((n.y - d.y) / 2), 10);
break;
case 'S':
l = parseInt(p.x - ((n.x - d.x) / 2), 10);
t = parseInt(p.y + d.y + 5, 10);
break;
case 'W':
l = parseInt(p.x - n.x - 5, 10);
t = parseInt(p.y - ((n.y - d.y) / 2), 10);
break;
}
return new JX.Vector(l, t);
},
isOnScreen: function (a, node) {
var view = this._getViewBoundaries();
var corners = this._getNodeCornerPositions(a, node);
// Check if any of the corners are offscreen.
for (var i = 0; i < corners.length; i++) {
var corner = corners[i];
if (corner.x < view.w ||
corner.y < view.n ||
corner.x > view.e ||
corner.y > view.s) {
return false;
}
}
return true;
},
_getNodeCornerPositions: function(pos, node) {
// Get positions of all four corners of a node.
var n = JX.Vector.getDim(node);
return [new JX.Vector(pos.x, pos.y),
new JX.Vector(pos.x + n.x, pos.y),
new JX.Vector(pos.x, pos.y + n.y),
new JX.Vector(pos.x + n.x, pos.y + n.y)];
},
_getViewBoundaries: function() {
var s = JX.Vector.getScroll();
var v = JX.Vector.getViewport();
var max_x = s.x + v.x;
var max_y = s.y + v.y;
// Even if the corner is technically on the screen, don't allow the
// tip to display too close to the edge of the screen.
var margin = 16;
return {
w: s.x + margin,
e: max_x - margin,
n: s.y + margin,
s: max_y - margin
};
},
_setAnchor: function (align) {
// Orient the little tail
JX.DOM.alterClass(this._node, 'jx-tooltip-align-' + align, true);
},
hide : function() {
if (this._node) {
JX.DOM.remove(this._node);
this._node = null;
this._last = new Date().getTime();
}
},
lock: function() {
var self = JX.Tooltip;
self.hide();
self._lock++;
},
unlock: function() {
var self = JX.Tooltip;
self._lock--;
}
}
});