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' => '519e8478', - 'core.pkg.js' => '14887b3d', + 'core.pkg.js' => '9494393a', 'darkconsole.pkg.js' => 'df001cab', 'differential.pkg.css' => '4a93db37', 'differential.pkg.js' => 'eb182ccd', @@ -415,7 +415,7 @@ 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => 'fe9a552f', 'rsrc/js/application/ponder/behavior-votebox.js' => '4e9b766b', 'rsrc/js/application/projects/behavior-boards-dropdown.js' => '0ec56e1d', - 'rsrc/js/application/projects/behavior-project-boards.js' => '21171a56', + 'rsrc/js/application/projects/behavior-project-boards.js' => 'a76e1483', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', 'rsrc/js/application/projects/behavior-reorder-columns.js' => '09eee344', 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', @@ -441,7 +441,7 @@ 'rsrc/js/application/uiexample/notification-example.js' => '7a9677fc', 'rsrc/js/core/Busy.js' => '6453c869', 'rsrc/js/core/DragAndDropFileUpload.js' => 'f61aa8ec', - 'rsrc/js/core/DraggableList.js' => 'bfccc644', + 'rsrc/js/core/DraggableList.js' => 'a1fe0641', 'rsrc/js/core/FileUpload.js' => 'a4ae61bf', 'rsrc/js/core/Hovercard.js' => '7e8468ae', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', @@ -639,7 +639,7 @@ 'javelin-behavior-policy-control' => 'f3fef818', 'javelin-behavior-policy-rule-editor' => 'fe9a552f', 'javelin-behavior-ponder-votebox' => '4e9b766b', - 'javelin-behavior-project-boards' => '21171a56', + 'javelin-behavior-project-boards' => 'a76e1483', 'javelin-behavior-project-create' => '065227cc', 'javelin-behavior-refresh-csrf' => '7814b593', 'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf', @@ -716,7 +716,7 @@ 'phabricator-crumbs-view-css' => '7fbf25b8', 'phabricator-dashboard-css' => 'a2bfdcbf', 'phabricator-drag-and-drop-file-upload' => 'f61aa8ec', - 'phabricator-draggable-list' => 'bfccc644', + 'phabricator-draggable-list' => 'a1fe0641', 'phabricator-fatal-config-template-css' => '25d446d6', 'phabricator-feed-css' => '4e544db4', 'phabricator-file-upload' => 'a4ae61bf', @@ -983,14 +983,6 @@ 'javelin-util', 'javelin-magical-init', ), - '21171a56' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', - ), '2290aeef' => array( 'javelin-install', 'javelin-dom', @@ -1466,6 +1458,14 @@ 'javelin-dom', 'javelin-reactor-dom', ), + 'a1fe0641' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + 'javelin-vector', + 'javelin-magical-init', + ), 'a4ae61bf' => array( 'javelin-install', 'javelin-dom', @@ -1487,6 +1487,14 @@ 'a5d7cf86' => array( 'javelin-dom', ), + 'a76e1483' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', + ), 'a80d0378' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1641,14 +1649,6 @@ 'javelin-util', 'phabricator-shaped-request', ), - 'bfccc644' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - 'javelin-vector', - 'javelin-magical-init', - ), 'c4569c05' => array( 'javelin-magical-init', 'javelin-install', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3831,6 +3831,7 @@ 'PassphraseCredential' => array( 'PassphraseDAO', 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', ), 'PassphraseCredentialControl' => 'AphrontFormControl', 'PassphraseCredentialCreateController' => 'PassphraseController', 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 @@ -95,7 +95,8 @@ for (ii = 0; ii < cols.length; ii++) { var list = new JX.DraggableList('project-card', cols[ii]) - .setFindItemsHandler(JX.bind(null, finditems, cols[ii])); + .setFindItemsHandler(JX.bind(null, finditems, cols[ii])) + .setOuterContainer(JX.$(config.boardID)); list.listen('didSend', JX.bind(list, onupdate, cols[ii])); list.listen('didReceive', 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 @@ -36,7 +36,8 @@ 'didReceive'], properties : { - findItemsHandler : null + findItemsHandler : null, + outerContainer: null }, members : { @@ -51,6 +52,9 @@ _ghostHandler : null, _ghostNode : null, _group : null, + _autoscroll: null, + _autoscroller: null, + _autotimer: null, getRootNode : function() { return this._root; @@ -170,6 +174,10 @@ this._originScroll = JX.Vector.getAggregateScrollForNode(this._dragging); this._dimensions = JX.$V(this._dragging); + 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(); this._group[ii]._generateTargets(); @@ -335,6 +343,58 @@ var p = JX.$V(e); + 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 = 48; + + 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; + var group = this._group; var target_list = this._getTargetList(p); @@ -401,6 +461,8 @@ var dragging = this._dragging; this._dragging = null; + clearInterval(this._autoscroller); + this._autoscroller = null; var target = false; var ghost = false; @@ -436,6 +498,69 @@ e.kill(); }, + _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();