1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-19 02:08:38 +01:00

Quicksand - make things work correctly with global drag and drop upload

Summary: Fixes T7685. This required making the global drag and drop behavior able to "uninstall" itself so to speak, and then it re-installs it self as necessary.

Test Plan:
Did the following all successfully

 - uploaded a file to homepage
 - homepage -> differential -- no way to upload via drag and drop
 - homepage -> differential -> homepage -- uploaded a file
 - homepage -> differential -> browser back button to homepage -- uploaded a file

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: Korvin, epriestley

Maniphest Tasks: T7685

Differential Revision: https://secure.phabricator.com/D12534
This commit is contained in:
Bob Trahan 2015-04-23 15:08:35 -07:00
parent cf154ae9f4
commit e40aa8f782
9 changed files with 179 additions and 87 deletions

View file

@ -8,10 +8,10 @@
return array( return array(
'names' => array( 'names' => array(
'core.pkg.css' => '9a9b59ca', 'core.pkg.css' => '9a9b59ca',
'core.pkg.js' => '348d5193', 'core.pkg.js' => '493cc6e6',
'darkconsole.pkg.js' => '8ab24e01', 'darkconsole.pkg.js' => '8ab24e01',
'differential.pkg.css' => '3500921f', 'differential.pkg.css' => '3500921f',
'differential.pkg.js' => 'c0506961', 'differential.pkg.js' => '890046d3',
'diffusion.pkg.css' => '591664fa', 'diffusion.pkg.css' => '591664fa',
'diffusion.pkg.js' => '0115b37c', 'diffusion.pkg.js' => '0115b37c',
'maniphest.pkg.css' => '68d4dd3d', 'maniphest.pkg.css' => '68d4dd3d',
@ -442,7 +442,7 @@ return array(
'rsrc/js/application/uiexample/gesture-example.js' => '558829c2', 'rsrc/js/application/uiexample/gesture-example.js' => '558829c2',
'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5', 'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5',
'rsrc/js/core/Busy.js' => '59a7976a', 'rsrc/js/core/Busy.js' => '59a7976a',
'rsrc/js/core/DragAndDropFileUpload.js' => '7fa4b248', 'rsrc/js/core/DragAndDropFileUpload.js' => '07de8873',
'rsrc/js/core/DraggableList.js' => 'a16ec1c6', 'rsrc/js/core/DraggableList.js' => 'a16ec1c6',
'rsrc/js/core/FileUpload.js' => '477359c8', 'rsrc/js/core/FileUpload.js' => '477359c8',
'rsrc/js/core/Hovercard.js' => '7e8468ae', 'rsrc/js/core/Hovercard.js' => '7e8468ae',
@ -468,7 +468,7 @@ return array(
'rsrc/js/core/behavior-file-tree.js' => '88236f00', 'rsrc/js/core/behavior-file-tree.js' => '88236f00',
'rsrc/js/core/behavior-form.js' => '5c54cbf3', 'rsrc/js/core/behavior-form.js' => '5c54cbf3',
'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c',
'rsrc/js/core/behavior-global-drag-and-drop.js' => 'bbdf75ca', 'rsrc/js/core/behavior-global-drag-and-drop.js' => '3f6075ff',
'rsrc/js/core/behavior-high-security-warning.js' => '8fc1c918', 'rsrc/js/core/behavior-high-security-warning.js' => '8fc1c918',
'rsrc/js/core/behavior-history-install.js' => '7ee2b591', 'rsrc/js/core/behavior-history-install.js' => '7ee2b591',
'rsrc/js/core/behavior-hovercard.js' => 'f36e01af', 'rsrc/js/core/behavior-hovercard.js' => 'f36e01af',
@ -593,7 +593,7 @@ return array(
'javelin-behavior-durable-column' => '657c2b50', 'javelin-behavior-durable-column' => '657c2b50',
'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-error-log' => '6882e80a',
'javelin-behavior-fancy-datepicker' => 'c51ae228', 'javelin-behavior-fancy-datepicker' => 'c51ae228',
'javelin-behavior-global-drag-and-drop' => 'bbdf75ca', 'javelin-behavior-global-drag-and-drop' => '3f6075ff',
'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-herald-rule-editor' => '7ebaeed3',
'javelin-behavior-high-security-warning' => '8fc1c918', 'javelin-behavior-high-security-warning' => '8fc1c918',
'javelin-behavior-history-install' => '7ee2b591', 'javelin-behavior-history-install' => '7ee2b591',
@ -726,7 +726,7 @@ return array(
'phabricator-core-css' => '76e8ee93', 'phabricator-core-css' => '76e8ee93',
'phabricator-countdown-css' => '86b7b0a0', 'phabricator-countdown-css' => '86b7b0a0',
'phabricator-dashboard-css' => '17937d22', 'phabricator-dashboard-css' => '17937d22',
'phabricator-drag-and-drop-file-upload' => '7fa4b248', 'phabricator-drag-and-drop-file-upload' => '07de8873',
'phabricator-draggable-list' => 'a16ec1c6', 'phabricator-draggable-list' => 'a16ec1c6',
'phabricator-fatal-config-template-css' => '8e6c6fcd', 'phabricator-fatal-config-template-css' => '8e6c6fcd',
'phabricator-feed-css' => 'b513b5f4', 'phabricator-feed-css' => 'b513b5f4',
@ -869,6 +869,14 @@ return array(
'phabricator-shaped-request', 'phabricator-shaped-request',
'conpherence-thread-manager', 'conpherence-thread-manager',
), ),
'07de8873' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-dom',
'javelin-uri',
'phabricator-file-upload',
),
'08883e8b' => array( '08883e8b' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-stratcom', 'javelin-stratcom',
@ -1080,6 +1088,13 @@ return array(
'javelin-dom', 'javelin-dom',
'phortune-credit-card-form', 'phortune-credit-card-form',
), ),
'3f6075ff' => array(
'javelin-behavior',
'javelin-dom',
'javelin-uri',
'javelin-mask',
'phabricator-drag-and-drop-file-upload',
),
'40a6a403' => array( '40a6a403' => array(
'javelin-install', 'javelin-install',
'javelin-dom', 'javelin-dom',
@ -1413,14 +1428,6 @@ return array(
'javelin-behavior', 'javelin-behavior',
'javelin-history', 'javelin-history',
), ),
'7fa4b248' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-dom',
'javelin-uri',
'phabricator-file-upload',
),
82439934 => array( 82439934 => array(
'javelin-behavior', 'javelin-behavior',
'javelin-dom', 'javelin-dom',
@ -1722,13 +1729,6 @@ return array(
'javelin-stratcom', 'javelin-stratcom',
'javelin-dom', 'javelin-dom',
), ),
'bbdf75ca' => array(
'javelin-behavior',
'javelin-dom',
'javelin-uri',
'javelin-mask',
'phabricator-drag-and-drop-file-upload',
),
'bd4c8dca' => array( 'bd4c8dca' => array(
'javelin-install', 'javelin-install',
'javelin-util', 'javelin-util',

View file

@ -57,6 +57,10 @@ abstract class PhabricatorController extends AphrontController {
return false; return false;
} }
public function isGlobalDragAndDropUploadEnabled() {
return false;
}
public function willBeginExecution() { public function willBeginExecution() {
$request = $this->getRequest(); $request = $this->getRequest();

View file

@ -2,19 +2,17 @@
final class PhabricatorFileListController extends PhabricatorFileController { final class PhabricatorFileListController extends PhabricatorFileController {
private $key;
public function shouldAllowPublic() { public function shouldAllowPublic() {
return true; return true;
} }
public function willProcessRequest(array $data) { public function isGlobalDragAndDropUploadEnabled() {
$this->key = idx($data, 'key'); return true;
} }
public function processRequest() { public function handleRequest(AphrontRequest $request) {
$controller = id(new PhabricatorApplicationSearchController()) $controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($this->key) ->setQueryKey($request->getURIData('key'))
->setSearchEngine(new PhabricatorFileSearchEngine()) ->setSearchEngine(new PhabricatorFileSearchEngine())
->setNavigation($this->buildSideNavView()); ->setNavigation($this->buildSideNavView());

View file

@ -2,8 +2,11 @@
final class PhabricatorFileUploadController extends PhabricatorFileController { final class PhabricatorFileUploadController extends PhabricatorFileController {
public function processRequest() { public function isGlobalDragAndDropUploadEnabled() {
$request = $this->getRequest(); return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getUser(); $viewer = $request->getUser();
$file = PhabricatorFile::initializeNewFile(); $file = PhabricatorFile::initializeNewFile();

View file

@ -1,5 +1,16 @@
<?php <?php
/**
* IMPORTANT: If you use this, make sure to implement
*
* public function isGlobalDragAndDropUploadEnabled() {
* return true;
* }
*
* on the controller(s) that render this class...! This is necessary
* to make sure Quicksand works properly with the javascript in this
* UI.
*/
final class PhabricatorGlobalUploadTargetView extends AphrontView { final class PhabricatorGlobalUploadTargetView extends AphrontView {
private $showIfSupportedID; private $showIfSupportedID;
@ -19,7 +30,7 @@ final class PhabricatorGlobalUploadTargetView extends AphrontView {
return null; return null;
} }
$instructions_id = celerity_generate_unique_node_id(); $instructions_id = 'phabricator-global-drag-and-drop-upload-instructions';
require_celerity_resource('global-drag-and-drop-css'); require_celerity_resource('global-drag-and-drop-css');

View file

@ -2,19 +2,18 @@
final class PhabricatorHomeMainController extends PhabricatorHomeController { final class PhabricatorHomeMainController extends PhabricatorHomeController {
private $only;
private $minipanels = array(); private $minipanels = array();
public function shouldAllowPublic() { public function shouldAllowPublic() {
return true; return true;
} }
public function willProcessRequest(array $data) { public function isGlobalDragAndDropUploadEnabled() {
$this->only = idx($data, 'only'); return true;
} }
public function processRequest() { public function handleRequest(AphrontRequest $request) {
$user = $this->getRequest()->getUser(); $user = $request->getUser();
$dashboard = PhabricatorDashboardInstall::getDashboard( $dashboard = PhabricatorDashboardInstall::getDashboard(
$user, $user,
@ -42,7 +41,7 @@ final class PhabricatorHomeMainController extends PhabricatorHomeController {
$content = $this->buildMainResponse($projects); $content = $this->buildMainResponse($projects);
} }
if (!$this->only) { if (!$request->getURIData('only')) {
$nav = $this->buildNav(); $nav = $this->buildNav();
$nav->appendChild( $nav->appendChild(
array( array(

View file

@ -605,6 +605,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
private function buildQuicksandConfig() { private function buildQuicksandConfig() {
$viewer = $this->getRequest()->getUser(); $viewer = $this->getRequest()->getUser();
$controller = $this->getController();
$dropdown_query = id(new AphlictDropdownDataQuery()) $dropdown_query = id(new AphlictDropdownDataQuery())
->setViewer($viewer); ->setViewer($viewer);
@ -624,7 +625,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
$rendered_dropdowns[$application_class] = $rendered_dropdowns[$application_class] =
$application->buildMainMenuExtraNodes( $application->buildMainMenuExtraNodes(
$viewer, $viewer,
$this->getController()); $controller);
} }
$console_config = null; $console_config = null;
@ -638,6 +639,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
$dropdown_query->getNotificationData(), $dropdown_query->getNotificationData(),
$dropdown_query->getConpherenceData(), $dropdown_query->getConpherenceData(),
), ),
'globalDragAndDrop' => $controller->isGlobalDragAndDropUploadEnabled(),
'aphlictDropdowns' => $rendered_dropdowns, 'aphlictDropdowns' => $rendered_dropdowns,
'consoleConfig' => $console_config, 'consoleConfig' => $console_config,
) + $this->buildAphlictListenConfigData(); ) + $this->buildAphlictListenConfigData();

View file

@ -40,6 +40,17 @@ JX.install('PhabricatorDragAndDropFileUpload', {
members : { members : {
_node : null, _node : null,
_depth : 0, _depth : 0,
_isEnabled: false,
setIsEnabled: function(bool) {
this._isEnabled = bool;
return this;
},
getIsEnabled: function() {
return this._isEnabled;
},
_updateDepth : function(delta) { _updateDepth : function(delta) {
if (this._depth === 0 && delta > 0) { if (this._depth === 0 && delta > 0) {
this.invoke('didBeginDrag'); this.invoke('didBeginDrag');
@ -54,6 +65,7 @@ JX.install('PhabricatorDragAndDropFileUpload', {
start : function() { start : function() {
// TODO: move this to JX.DOM.contains()? // TODO: move this to JX.DOM.contains()?
function contains(container, child) { function contains(container, child) {
do { do {
@ -73,6 +85,9 @@ JX.install('PhabricatorDragAndDropFileUpload', {
'click', 'click',
null, null,
JX.bind(this, function (e) { JX.bind(this, function (e) {
if (!this.getIsEnabled()) {
return;
}
if (this._depth) { if (this._depth) {
e.kill(); e.kill();
// Force depth to 0. // Force depth to 0.
@ -87,6 +102,10 @@ JX.install('PhabricatorDragAndDropFileUpload', {
'dragenter', 'dragenter',
null, null,
JX.bind(this, function(e) { JX.bind(this, function(e) {
if (!this.getIsEnabled()) {
return;
}
if (contains(this._node, e.getTarget())) { if (contains(this._node, e.getTarget())) {
this._updateDepth(1); this._updateDepth(1);
} }
@ -97,6 +116,10 @@ JX.install('PhabricatorDragAndDropFileUpload', {
'dragleave', 'dragleave',
null, null,
JX.bind(this, function(e) { JX.bind(this, function(e) {
if (!this.getIsEnabled()) {
return;
}
if (contains(this._node, e.getTarget())) { if (contains(this._node, e.getTarget())) {
this._updateDepth(-1); this._updateDepth(-1);
} }
@ -106,18 +129,26 @@ JX.install('PhabricatorDragAndDropFileUpload', {
this._node, this._node,
'dragover', 'dragover',
null, null,
function(e) { JX.bind(this, function(e) {
if (!this.getIsEnabled()) {
return;
}
// NOTE: We must set this, or Chrome refuses to drop files from the // NOTE: We must set this, or Chrome refuses to drop files from the
// download shelf. // download shelf.
e.getRawEvent().dataTransfer.dropEffect = 'copy'; e.getRawEvent().dataTransfer.dropEffect = 'copy';
e.kill(); e.kill();
}); }));
JX.DOM.listen( JX.DOM.listen(
this._node, this._node,
'drop', 'drop',
null, null,
JX.bind(this, function(e) { JX.bind(this, function(e) {
if (!this.getIsEnabled()) {
return;
}
e.kill(); e.kill();
var files = e.getRawEvent().dataTransfer.files; var files = e.getRawEvent().dataTransfer.files;
@ -135,6 +166,10 @@ JX.install('PhabricatorDragAndDropFileUpload', {
'paste', 'paste',
null, null,
JX.bind(this, function(e) { JX.bind(this, function(e) {
if (!this.getIsEnabled()) {
return;
}
var clipboard = e.getRawEvent().clipboardData; var clipboard = e.getRawEvent().clipboardData;
if (!clipboard) { if (!clipboard) {
return; return;
@ -168,6 +203,8 @@ JX.install('PhabricatorDragAndDropFileUpload', {
} }
})); }));
} }
this.setIsEnabled(true);
}, },
_sendRequest : function(spec) { _sendRequest : function(spec) {

View file

@ -7,64 +7,102 @@
* phabricator-drag-and-drop-file-upload * phabricator-drag-and-drop-file-upload
*/ */
JX.behavior('global-drag-and-drop', function(config) { JX.behavior('global-drag-and-drop', function(config, statics) {
if (!JX.PhabricatorDragAndDropFileUpload.isSupported()) { if (!JX.PhabricatorDragAndDropFileUpload.isSupported()) {
return; return;
} }
var pending = 0; function init() {
var files = []; statics.pending = 0;
var errors = false; statics.files = [];
statics.errors = false;
statics.enabled = true;
if (config.ifSupported) { if (config.ifSupported) {
JX.$(config.ifSupported).style.display = ''; JX.$(config.ifSupported).style.display = '';
}
var page = JX.$('phabricator-standard-page');
statics.drop = new JX.PhabricatorDragAndDropFileUpload(page)
.setURI(config.uploadURI)
.setViewPolicy(config.viewPolicy)
.setChunkThreshold(config.chunkThreshold);
install_extra_listeners();
statics.drop.start();
return true;
} }
var page = JX.$('phabricator-standard-page'); function install_extra_listeners() {
var drop = new JX.PhabricatorDragAndDropFileUpload(page) statics.drop.listen('didBeginDrag', function() {
.setURI(config.uploadURI) if (!statics.enabled) {
.setViewPolicy(config.viewPolicy) return;
.setChunkThreshold(config.chunkThreshold);
drop.listen('didBeginDrag', function() {
JX.Mask.show('global-upload-mask');
JX.DOM.show(JX.$(config.instructions));
});
drop.listen('didEndDrag', function() {
JX.Mask.hide('global-upload-mask');
JX.DOM.hide(JX.$(config.instructions));
});
drop.listen('willUpload', function() {
pending++;
});
drop.listen('didUpload', function(f) {
files.push(f);
pending--;
if (pending === 0 && !errors) {
// If whatever the user dropped in has finished uploading, send them to
// their uploads.
var uri;
uri = JX.$U(config.browseURI);
var ids = [];
for (var ii = 0; ii < files.length; ii++) {
ids.push(files[ii].getID());
} }
uri.setQueryParam('h', ids.join(',')); JX.Mask.show('global-upload-mask');
JX.DOM.show(JX.$(config.instructions));
});
files = []; statics.drop.listen('didEndDrag', function() {
if (!statics.enabled) {
return;
}
JX.Mask.hide('global-upload-mask');
JX.DOM.hide(JX.$(config.instructions));
});
uri.go(); statics.drop.listen('willUpload', function() {
} if (!statics.enabled) {
}); return;
}
statics.pending++;
});
drop.listen('didError', function() { statics.drop.listen('didUpload', function(f) {
pending--; if (!statics.enabled) {
errors = true; return;
}); }
statics.files.push(f);
drop.start(); statics.pending--;
if (statics.pending === 0 && !statics.errors) {
// If whatever the user dropped in has finished uploading, send them to
// their uploads.
var uri;
uri = JX.$U(config.browseURI);
var ids = [];
for (var ii = 0; ii < statics.files.length; ii++) {
ids.push(statics.files[ii].getID());
}
uri.setQueryParam('h', ids.join(','));
statics.files = [];
uri.go();
}
});
statics.drop.listen('didError', function() {
if (!statics.enabled) {
return;
}
statics.pending--;
statics.errors = true;
});
}
statics.init = statics.init || init();
JX.Stratcom.listen(
'quicksand-redraw',
null,
function (e) {
e.kill();
var data = e.getData();
var toggle = data.newResponse.globalDragAndDrop;
statics.enabled = toggle;
statics.drop.setIsEnabled(toggle);
});
}); });