diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => '764d4c80', - 'core.pkg.js' => '5b832397', + 'core.pkg.js' => '53c6a7c5', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', 'differential.pkg.js' => '5c2ba922', @@ -413,7 +413,7 @@ 'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef', 'rsrc/js/application/policy/behavior-policy-control.js' => 'ae45872f', 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', - 'rsrc/js/application/projects/behavior-project-boards.js' => '16c76360', + 'rsrc/js/application/projects/behavior-project-boards.js' => 'c05fb42a', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', 'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb', 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', @@ -446,7 +446,7 @@ 'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5', 'rsrc/js/core/Busy.js' => '59a7976a', 'rsrc/js/core/DragAndDropFileUpload.js' => 'ad10aeac', - 'rsrc/js/core/DraggableList.js' => '1fe26f18', + 'rsrc/js/core/DraggableList.js' => '8905523d', 'rsrc/js/core/FileUpload.js' => '477359c8', 'rsrc/js/core/Hovercard.js' => 'c6f720ff', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', @@ -653,7 +653,7 @@ 'javelin-behavior-phui-profile-menu' => '12884df9', 'javelin-behavior-policy-control' => 'ae45872f', 'javelin-behavior-policy-rule-editor' => '5e9f347c', - 'javelin-behavior-project-boards' => '16c76360', + 'javelin-behavior-project-boards' => 'c05fb42a', 'javelin-behavior-project-create' => '065227cc', 'javelin-behavior-quicksand-blacklist' => '7927a7d3', 'javelin-behavior-recurring-edit' => '5f1c4d5f', @@ -741,7 +741,7 @@ 'phabricator-countdown-css' => 'e7544472', 'phabricator-dashboard-css' => 'eb458607', 'phabricator-drag-and-drop-file-upload' => 'ad10aeac', - 'phabricator-draggable-list' => '1fe26f18', + 'phabricator-draggable-list' => '8905523d', 'phabricator-fatal-config-template-css' => '8e6c6fcd', 'phabricator-feed-css' => 'ecd4ec57', 'phabricator-file-upload' => '477359c8', @@ -953,15 +953,6 @@ 'javelin-dom', 'javelin-history', ), - '16c76360' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', - ), '1ad0a787' => array( 'javelin-install', 'javelin-reactor', @@ -1007,14 +998,6 @@ 'phuix-icon-view', 'javelin-behavior-phabricator-gesture', ), - '1fe26f18' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - 'javelin-vector', - 'javelin-magical-init', - ), '21ba5861' => array( 'javelin-behavior', 'javelin-dom', @@ -1492,6 +1475,14 @@ 'javelin-stratcom', 'javelin-dom', ), + '8905523d' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + 'javelin-vector', + 'javelin-magical-init', + ), '8a41885b' => array( 'javelin-install', 'javelin-dom', @@ -1781,6 +1772,15 @@ 'javelin-install', 'javelin-dom', ), + 'c05fb42a' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', + ), 'c1700f6f' => array( 'javelin-install', 'javelin-util', diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js --- a/webroot/rsrc/js/application/projects/behavior-project-boards.js +++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js @@ -232,6 +232,7 @@ for (ii = 0; ii < cols.length; ii++) { var list = new JX.DraggableList('project-card', cols[ii]) .setFindItemsHandler(JX.bind(null, finditems, cols[ii])) + .setOuterContainer(JX.$(config.boardID)) .setCanDragX(true); list.listen('didSend', JX.bind(list, onupdate, cols[ii])); diff --git a/webroot/rsrc/js/core/DraggableList.js b/webroot/rsrc/js/core/DraggableList.js --- a/webroot/rsrc/js/core/DraggableList.js +++ b/webroot/rsrc/js/core/DraggableList.js @@ -39,7 +39,8 @@ properties : { findItemsHandler: null, - canDragX: false + canDragX: false, + outerContainer: null }, members : { @@ -51,10 +52,15 @@ _ghostHandler : null, _ghostNode : null, _group : null, - _lastMousePosition: null, + _cursorPosition: null, + _cursorOrigin: null, + _cursorScroll: null, _frame: null, _clone: null, _offset: null, + _autoscroll: null, + _autoscroller: null, + _autotimer: null, getRootNode : function() { return this._root; @@ -177,6 +183,10 @@ var drag = e.getNode(this._sigil); + this._autoscroll = {}; + this._autoscroller = setInterval(JX.bind(this, this._onautoscroll), 10); + this._autotimer = null; + for (var ii = 0; ii < this._group.length; ii++) { this._group[ii]._clearTarget(); } @@ -398,14 +408,16 @@ // reuse the known position. if (e.getType() == 'mousemove') { - this._lastMousePosition = JX.$V(e); + this._cursorPosition = JX.$V(e); + this._cursorOrigin = JX.$V(e); + this._cursorScroll = JX.Vector.getScroll(); } if (!this._dragging) { return; } - if (!this._lastMousePosition) { + if (!this._cursorPosition) { return; } @@ -413,9 +425,17 @@ // If this is a scroll event, the positions of drag targets may have // changed. this._dirtyTargetCache(); + + // Correct the cursor position to account for scrolling. + var s = JX.Vector.getScroll(); + this._cursorPosition = new JX.$V( + this._cursorOrigin.x - (this._cursorScroll.x - s.x), + this._cursorOrigin.y - (this._cursorScroll.y - s.y)); } - var p = JX.$V(this._lastMousePosition.x, this._lastMousePosition.y); + this._updateAutoscroll(this._cursorPosition); + + var p = JX.$V(this._cursorPosition.x, this._cursorPosition.y); var group = this._group; var target_list = this._getTargetList(p); @@ -454,6 +474,60 @@ e.kill(); }, + _updateAutoscroll: function(p) { + var container = this._dragging.parentNode; + var autoscroll = {}; + + var outer = this.getOuterContainer(); + + var cpos; + var cdim; + + while (container) { + if (outer && (container == outer)) { + break; + } + + try { + cpos = JX.Vector.getPos(container); + cdim = JX.Vector.getDim(container); + if (container == document.body) { + cdim = JX.Vector.getViewport(); + cpos.x += container.scrollLeft; + cpos.y += container.scrollTop; + } + } catch (ignored) { + break; + } + + var fuzz = 64; + + if (p.y <= cpos.y + fuzz) { + autoscroll.up = container; + } + + if (p.y >= cpos.y + cdim.y - fuzz) { + autoscroll.down = container; + } + + if (p.x <= cpos.x + fuzz) { + autoscroll.left = container; + } + + if (p.x >= cpos.x + cdim.x - fuzz) { + autoscroll.right = container; + } + + if (container == document.body) { + break; + } + + container = container.parentNode; + } + + this._autoscroll = autoscroll; + }, + _onkey: function(e) { // Cancel any current drag if the user presses escape. if (this._dragging && (e.getSpecialKey() == 'esc')) { @@ -464,6 +538,10 @@ }, _ondrop : function(e) { + if (this._dragging) { + e.kill(); + } + var p = JX.$V(e); this._drop(p); }, @@ -475,6 +553,8 @@ var dragging = this._dragging; this._dragging = null; + clearInterval(this._autoscroller); + this._autoscroller = null; JX.DOM.remove(this._frame); this._frame = null; @@ -512,11 +592,72 @@ JX.DOM.alterClass(dragging, 'drag-dragging', false); JX.Tooltip.unlock(); - e.kill(); - this.invoke('didEndDrag', dragging); }, + _onautoscroll: function() { + var u = this._autoscroll.up; + var d = this._autoscroll.down; + var l = this._autoscroll.left; + var r = this._autoscroll.right; + + var now = +new Date(); + + if (!this._autotimer) { + this._autotimer = now; + return; + } + + var delta = now - this._autotimer; + this._autotimer = now; + + var amount = 12 * (delta / 10); + + if (u && (u != d)) { + this._tryScroll(this._dragging, u, 'scrollTop', amount); + } + + if (d && (d != u)) { + this._tryScroll(this._dragging, d, 'scrollTop', -amount); + } + + if (l && (l != r)) { + this._tryScroll(this._dragging, l, 'scrollLeft', amount); + } + + if (r && (r != l)) { + this._tryScroll(this._dragging, r, 'scrollLeft', -amount); + } + }, + + /** + * Walk up the tree from a node to some parent, trying to scroll every + * container. Stop when we find a container which we're able to scroll. + */ + _tryScroll: function(from, to, property, amount) { + var value; + + var container = from.parentNode; + while (container) { + // Read the current scroll value. + value = container[property]; + + // Try to scroll. + container[property] -= amount; + + // If we scrolled it, we're all done. + if (container[property] != value) { + break; + } + + if (container == to) { + break; + } + + container = container.parentNode; + } + }, + lock : function() { for (var ii = 0; ii < this._group.length; ii++) { this._group[ii]._lock();