mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 00:42:41 +01:00
When dragging nodes, clone them
Summary: Ref T5240. Currently, when dragging nodes, we leave them where they are in the document and apply "position: relative;" so we can move them around on screen. - Pros: All the CSS still works. - Cons: Can't drag them outside the nearest containing element with "overflow: hidden;", many subtle positioning bugs with scrollable containers. Instead, this diff leaves the thing we're dragging exactly where it is, clones it, and drags the clone instead. - Pros: You can drag it anywhere. Seems to fix all the scrolling container problems. - Cons: CSS which depends on a container class no longer works. The CSS thing is bad, but doesn't seem too unreasonable to fix. Basically, we just need to put some `phui-this-is-a-workboard-card` class on the cards, and use that to style them instead of `phui-workboard-view`, and then do something similar for draggable lists. Although we no longer need to drag cards to tabs with the current design, I think there's a reasonable chance we'll revisit that later. The current design also calls for scrollable columns, but there would be no way to drag cards outside of their current column with the current approach. NOTE: This does not attempt to fix the CSS, so dragging is pretty rough, since the "clone" loses a number of container classes and thus a number of rules. I'll clean up the CSS in the next change. Test Plan: - Dragged stuff around on task lists, workboards, and sort lists (e.g., pinned applications) in Safari, Firefox and Chrome. - Scrolled window and containers (workboards) during drag. - Dragged stuff out of the workboard. - Dragged stuff offscreen. - CSS is funky, but I can no longer find any positioning or layout issues in any browser. Reviewers: chad Reviewed By: chad Maniphest Tasks: T5240 Differential Revision: https://secure.phabricator.com/D15160
This commit is contained in:
parent
730de1b6e5
commit
fce0109822
5 changed files with 137 additions and 86 deletions
|
@ -7,8 +7,8 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'core.pkg.css' => '8b9c004a',
|
||||
'core.pkg.js' => 'bf947f93',
|
||||
'core.pkg.css' => 'cd66e467',
|
||||
'core.pkg.js' => 'c5178ede',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => '2de124c9',
|
||||
'differential.pkg.js' => '5c2ba922',
|
||||
|
@ -105,7 +105,7 @@ return array(
|
|||
'rsrc/css/core/core.css' => '5b3563c8',
|
||||
'rsrc/css/core/remarkup.css' => 'e1c8b32f',
|
||||
'rsrc/css/core/syntax.css' => '9fd11da8',
|
||||
'rsrc/css/core/z-index.css' => 'a36a45da',
|
||||
'rsrc/css/core/z-index.css' => '5c7025bf',
|
||||
'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa',
|
||||
'rsrc/css/font/font-aleo.css' => '8bdb2835',
|
||||
'rsrc/css/font/font-awesome.css' => 'c43323c5',
|
||||
|
@ -142,7 +142,7 @@ return array(
|
|||
'rsrc/css/phui/phui-info-view.css' => '6d7c3509',
|
||||
'rsrc/css/phui/phui-list.css' => '9da2aa00',
|
||||
'rsrc/css/phui/phui-object-box.css' => '407eaf5a',
|
||||
'rsrc/css/phui/phui-object-item-list-view.css' => 'febf4a79',
|
||||
'rsrc/css/phui/phui-object-item-list-view.css' => '699de05e',
|
||||
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
|
||||
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
|
||||
'rsrc/css/phui/phui-profile-menu.css' => 'ab4fcf5f',
|
||||
|
@ -446,7 +446,7 @@ return array(
|
|||
'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' => '255d85da',
|
||||
'rsrc/js/core/DraggableList.js' => 'f1844746',
|
||||
'rsrc/js/core/FileUpload.js' => '477359c8',
|
||||
'rsrc/js/core/Hovercard.js' => 'c6f720ff',
|
||||
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
|
||||
|
@ -741,7 +741,7 @@ return array(
|
|||
'phabricator-countdown-css' => 'e7544472',
|
||||
'phabricator-dashboard-css' => 'eb458607',
|
||||
'phabricator-drag-and-drop-file-upload' => 'ad10aeac',
|
||||
'phabricator-draggable-list' => '255d85da',
|
||||
'phabricator-draggable-list' => 'f1844746',
|
||||
'phabricator-fatal-config-template-css' => '8e6c6fcd',
|
||||
'phabricator-feed-css' => 'ecd4ec57',
|
||||
'phabricator-file-upload' => '477359c8',
|
||||
|
@ -780,7 +780,7 @@ return array(
|
|||
'phabricator-uiexample-reactor-select' => 'a155550f',
|
||||
'phabricator-uiexample-reactor-sendclass' => '1def2711',
|
||||
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
|
||||
'phabricator-zindex-css' => 'a36a45da',
|
||||
'phabricator-zindex-css' => '5c7025bf',
|
||||
'phame-css' => '6d5b3682',
|
||||
'pholio-css' => '95174bdd',
|
||||
'pholio-edit-css' => '3ad9d1ee',
|
||||
|
@ -818,7 +818,7 @@ return array(
|
|||
'phui-inline-comment-view-css' => '0fdb3667',
|
||||
'phui-list-view-css' => '9da2aa00',
|
||||
'phui-object-box-css' => '407eaf5a',
|
||||
'phui-object-item-list-view-css' => 'febf4a79',
|
||||
'phui-object-item-list-view-css' => '699de05e',
|
||||
'phui-pager-css' => 'bea33d23',
|
||||
'phui-pinboard-view-css' => '2495140e',
|
||||
'phui-profile-menu-css' => 'ab4fcf5f',
|
||||
|
@ -1021,14 +1021,6 @@ return array(
|
|||
'phabricator-drag-and-drop-file-upload',
|
||||
'phabricator-draggable-list',
|
||||
),
|
||||
'255d85da' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'2926fff2' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -2019,6 +2011,14 @@ return array(
|
|||
'javelin-workflow',
|
||||
'javelin-json',
|
||||
),
|
||||
'f1844746' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'f411b6ae' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
|
|
@ -383,17 +383,24 @@ final class PHUIObjectItemView extends AphrontTagView {
|
|||
),
|
||||
$this->header);
|
||||
|
||||
$header = javelin_tag(
|
||||
// Wrap the header content in a <span> with the "slippery" sigil. This
|
||||
// prevents us from beginning a drag if you click the text (like "T123"),
|
||||
// but not if you click the white space after the header.
|
||||
$header = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phui-object-item-name',
|
||||
'sigil' => 'slippery',
|
||||
),
|
||||
array(
|
||||
$this->headIcons,
|
||||
$header_name,
|
||||
$header_link,
|
||||
));
|
||||
javelin_tag(
|
||||
'span',
|
||||
array(
|
||||
'sigil' => 'slippery',
|
||||
),
|
||||
array(
|
||||
$this->headIcons,
|
||||
$header_name,
|
||||
$header_link,
|
||||
)));
|
||||
|
||||
$icons = array();
|
||||
if ($this->icons) {
|
||||
|
|
|
@ -81,10 +81,6 @@ div.phui-calendar-day-event {
|
|||
z-index: 5;
|
||||
}
|
||||
|
||||
.drag-dragging {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.phui-calendar-date-number {
|
||||
z-index: 5;
|
||||
}
|
||||
|
@ -114,6 +110,10 @@ div.phui-calendar-day-event {
|
|||
z-index: 9;
|
||||
}
|
||||
|
||||
.drag-frame {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.jx-mask {
|
||||
z-index: 10;
|
||||
}
|
||||
|
|
|
@ -593,15 +593,36 @@ ul.phui-object-item-list-view .phui-object-item-selected
|
|||
}
|
||||
|
||||
.drag-dragging {
|
||||
position: relative;
|
||||
background: {$sh-yellowbackground};
|
||||
opacity: 0.9;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.drag-sending {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.drag-clone,
|
||||
.drag-frame {
|
||||
/* This allows mousewheel events to pass through the clone and frame while
|
||||
they are being dragged. Without this, the mousewheel does not work during
|
||||
a drag operation. */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.drag-frame {
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.drag-clone {
|
||||
position: absolute;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
|
||||
/* - State ---------------------------------------------------------------------
|
||||
|
||||
Provides a list of object status or states, success or fail, etc
|
||||
|
|
|
@ -44,15 +44,15 @@ JX.install('DraggableList', {
|
|||
_root : null,
|
||||
_dragging : null,
|
||||
_locked : 0,
|
||||
_origin : null,
|
||||
_originScroll : null,
|
||||
_target : null,
|
||||
_targets : null,
|
||||
_ghostHandler : null,
|
||||
_ghostNode : null,
|
||||
_group : null,
|
||||
_lastMousePosition: null,
|
||||
_lastAdjust: null,
|
||||
_frame: null,
|
||||
_clone: null,
|
||||
_offset: null,
|
||||
|
||||
getRootNode : function() {
|
||||
return this._root;
|
||||
|
@ -131,7 +131,17 @@ JX.install('DraggableList', {
|
|||
}
|
||||
}
|
||||
|
||||
return handler();
|
||||
var items = handler();
|
||||
|
||||
// Make sure the clone element is never included as a target.
|
||||
for (var ii = 0; ii < items.length; ii++) {
|
||||
if (items[ii] === this._clone) {
|
||||
items.splice(ii, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
_ondrag : function(e) {
|
||||
|
@ -167,24 +177,67 @@ JX.install('DraggableList', {
|
|||
|
||||
e.kill();
|
||||
|
||||
this._dragging = e.getNode(this._sigil);
|
||||
this._origin = JX.$V(e);
|
||||
this._originScroll = JX.Vector.getAggregateScrollForNode(this._dragging);
|
||||
var drag = e.getNode(this._sigil);
|
||||
|
||||
for (var ii = 0; ii < this._group.length; ii++) {
|
||||
this._group[ii]._clearTarget();
|
||||
}
|
||||
|
||||
if (!this.invoke('didBeginDrag', this._dragging).getPrevented()) {
|
||||
// Set the height of all the ghosts in the group. In the normal case,
|
||||
// this just sets this list's ghost height.
|
||||
for (var jj = 0; jj < this._group.length; jj++) {
|
||||
var ghost = this._group[jj].getGhostNode();
|
||||
ghost.style.height = JX.Vector.getDim(this._dragging).y + 'px';
|
||||
}
|
||||
var pos = JX.$V(drag);
|
||||
var dim = JX.Vector.getDim(drag);
|
||||
|
||||
JX.DOM.alterClass(this._dragging, 'drag-dragging', true);
|
||||
// Create and adjust the ghost nodes.
|
||||
for (var jj = 0; jj < this._group.length; jj++) {
|
||||
var ghost = this._group[jj].getGhostNode();
|
||||
ghost.style.height = dim.y + 'px';
|
||||
}
|
||||
|
||||
// Here's what's going on: we're cloning the thing that's being dragged.
|
||||
// This is the "clone", stored in "this._clone". We're going to leave the
|
||||
// original where it is in the document, and put the clone at top-level
|
||||
// so it can be freely dragged around the whole document, even if it's
|
||||
// inside a container with overflow hidden.
|
||||
|
||||
// Because the clone has been moved up, CSS classes which rely on some
|
||||
// parent selector won't work. Draggable objects need to pick up all of
|
||||
// their CSS properties without relying on container classes. This isn't
|
||||
// great, but leaving them where they are in the document creates a large
|
||||
// number of positioning problems with scrollable, absolute, relative,
|
||||
// or overflow hidden containers.
|
||||
|
||||
// Note that we don't actually want to let the user drag it outside the
|
||||
// document. One problem is that doing so lets the user drag objects
|
||||
// infinitely far to the right by dragging them to the edge so the
|
||||
// document extends, scrolling the document, dragging them to the edge
|
||||
// of the new larger document, scrolling the document, and so on forever.
|
||||
|
||||
// To prevent this, we're putting a "frame" (stored in "this._frame") at
|
||||
// top level, then putting the clone inside the frame. The frame has the
|
||||
// same size as the entire viewport, and overflow hidden, so dragging the
|
||||
// item outside the document just cuts it off.
|
||||
|
||||
// Create the clone for dragging.
|
||||
var clone = drag.cloneNode(true);
|
||||
|
||||
pos.setPos(clone);
|
||||
dim.setDim(clone);
|
||||
|
||||
JX.DOM.alterClass(drag, 'drag-dragging', true);
|
||||
JX.DOM.alterClass(clone, 'drag-clone', true);
|
||||
|
||||
var frame = JX.$N('div', {className: 'drag-frame'});
|
||||
frame.appendChild(clone);
|
||||
|
||||
document.body.appendChild(frame);
|
||||
|
||||
this._dragging = drag;
|
||||
this._clone = clone;
|
||||
this._frame = frame;
|
||||
|
||||
var cursor = JX.$V(e);
|
||||
this._offset = new JX.Vector(pos.x - cursor.x, pos.y - cursor.y);
|
||||
|
||||
this.invoke('didBeginDrag', this._dragging);
|
||||
},
|
||||
|
||||
_getTargets : function() {
|
||||
|
@ -195,18 +248,6 @@ JX.install('DraggableList', {
|
|||
var item = items[ii];
|
||||
|
||||
var ipos = JX.$V(item);
|
||||
if (item == this._dragging) {
|
||||
// If the item we're measuring is also the item we're dragging,
|
||||
// we need to measure its position as though it was still in the
|
||||
// list, not its current position in the document (which is
|
||||
// under the cursor). To do this, adjust the measured position by
|
||||
// removing the offsets we added to put the item underneath the
|
||||
// cursor.
|
||||
if (this._lastAdjust) {
|
||||
ipos.x -= this._lastAdjust.x;
|
||||
ipos.y -= this._lastAdjust.y;
|
||||
}
|
||||
}
|
||||
|
||||
targets.push({
|
||||
item: items[ii],
|
||||
|
@ -398,39 +439,18 @@ JX.install('DraggableList', {
|
|||
}
|
||||
}
|
||||
|
||||
// If the drop target indicator is above the cursor in the document,
|
||||
// adjust the cursor position for the change in node document position.
|
||||
// Do this before choosing a new target to avoid a flash of nonsense.
|
||||
var f = JX.$V(this._frame);
|
||||
p.x -= f.x;
|
||||
p.y -= f.y;
|
||||
|
||||
var scroll = JX.Vector.getAggregateScrollForNode(this._dragging);
|
||||
|
||||
var origin = {
|
||||
x: this._origin.x + (this._originScroll.x - scroll.x),
|
||||
y: this._origin.y + (this._originScroll.y - scroll.y)
|
||||
};
|
||||
|
||||
var adjust_h = 0;
|
||||
var adjust_y = 0;
|
||||
if (this._target !== false) {
|
||||
var ghost = this.getGhostNode();
|
||||
adjust_h = JX.Vector.getDim(ghost).y;
|
||||
adjust_y = JX.$V(ghost).y;
|
||||
|
||||
if (adjust_y <= origin.y) {
|
||||
p.y -= adjust_h;
|
||||
}
|
||||
}
|
||||
p.y += this._offset.y;
|
||||
this._clone.style.top = p.y + 'px';
|
||||
|
||||
if (this._canDragX()) {
|
||||
p.x -= origin.x;
|
||||
} else {
|
||||
p.x = 0;
|
||||
p.x += this._offset.x;
|
||||
this._clone.style.left = p.x + 'px';
|
||||
}
|
||||
|
||||
p.y -= origin.y;
|
||||
this._lastAdjust = new JX.Vector(p.x, p.y);
|
||||
p.setPos(this._dragging);
|
||||
|
||||
e.kill();
|
||||
},
|
||||
|
||||
|
@ -444,6 +464,10 @@ JX.install('DraggableList', {
|
|||
var dragging = this._dragging;
|
||||
this._dragging = null;
|
||||
|
||||
JX.DOM.remove(this._frame);
|
||||
this._frame = null;
|
||||
this._clone = null;
|
||||
|
||||
var target = false;
|
||||
var ghost = false;
|
||||
|
||||
|
@ -469,7 +493,6 @@ JX.install('DraggableList', {
|
|||
for (var ii = 0; ii < group.length; ii++) {
|
||||
JX.DOM.alterClass(group[ii].getRootNode(), 'drag-target-list', false);
|
||||
group[ii]._clearTarget();
|
||||
group[ii]._lastAdjust = null;
|
||||
}
|
||||
|
||||
if (!this.invoke('didEndDrag', dragging).getPrevented()) {
|
||||
|
|
Loading…
Reference in a new issue