Page MenuHomePhabricator

D15953.diff
No OneTemporary

D15953.diff

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
@@ -1594,6 +1594,7 @@
'PHUIFeedStoryExample' => 'applications/uiexample/examples/PHUIFeedStoryExample.php',
'PHUIFeedStoryView' => 'view/phui/PHUIFeedStoryView.php',
'PHUIFormDividerControl' => 'view/form/control/PHUIFormDividerControl.php',
+ 'PHUIFormFileControl' => 'view/form/control/PHUIFormFileControl.php',
'PHUIFormFreeformDateControl' => 'view/form/control/PHUIFormFreeformDateControl.php',
'PHUIFormIconSetControl' => 'view/form/control/PHUIFormIconSetControl.php',
'PHUIFormInsetView' => 'view/form/PHUIFormInsetView.php',
@@ -5996,6 +5997,7 @@
'PHUIFeedStoryExample' => 'PhabricatorUIExample',
'PHUIFeedStoryView' => 'AphrontView',
'PHUIFormDividerControl' => 'AphrontFormControl',
+ 'PHUIFormFileControl' => 'AphrontFormControl',
'PHUIFormFreeformDateControl' => 'AphrontFormControl',
'PHUIFormIconSetControl' => 'AphrontFormControl',
'PHUIFormInsetView' => 'AphrontView',
diff --git a/src/applications/files/controller/PhabricatorFileDropUploadController.php b/src/applications/files/controller/PhabricatorFileDropUploadController.php
--- a/src/applications/files/controller/PhabricatorFileDropUploadController.php
+++ b/src/applications/files/controller/PhabricatorFileDropUploadController.php
@@ -56,7 +56,7 @@
$file_phid = $result['filePHID'];
if ($file_phid) {
$file = $this->loadFile($file_phid);
- $result += $this->getFileDictionary($file);
+ $result += $file->getDragAndDropDictionary();
}
return id(new AphrontAjaxResponse())->setContent($result);
@@ -84,7 +84,7 @@
} else {
$result = array(
'complete' => true,
- ) + $this->getFileDictionary($file);
+ ) + $file->getDragAndDropDictionary();
}
return id(new AphrontAjaxResponse())->setContent($result);
@@ -99,18 +99,10 @@
'isExplicitUpload' => true,
));
- $result = $this->getFileDictionary($file);
+ $result = $file->getDragAndDropDictionary();
return id(new AphrontAjaxResponse())->setContent($result);
}
- private function getFileDictionary(PhabricatorFile $file) {
- return array(
- 'id' => $file->getID(),
- 'phid' => $file->getPHID(),
- 'uri' => $file->getBestURI(),
- );
- }
-
private function loadFile($file_phid) {
$viewer = $this->getViewer();
diff --git a/src/applications/files/controller/PhabricatorFileUploadDialogController.php b/src/applications/files/controller/PhabricatorFileUploadDialogController.php
--- a/src/applications/files/controller/PhabricatorFileUploadDialogController.php
+++ b/src/applications/files/controller/PhabricatorFileUploadDialogController.php
@@ -6,12 +6,51 @@
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
- return $this->newDialog()
- ->setTitle(pht('Upload File'))
- ->appendChild(pht(
- 'To add files, drag and drop them into the comment text area.'))
- ->addCancelButton('/', pht('Close'));
+ $e_file = true;
+ $errors = array();
+ if ($request->isDialogFormPost()) {
+ $file_phids = $request->getStrList('filePHIDs');
+ if ($file_phids) {
+ $files = id(new PhabricatorFileQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($file_phids)
+ ->setRaisePolicyExceptions(true)
+ ->execute();
+ } else {
+ $files = array();
+ }
+
+ if ($files) {
+ $results = array();
+ foreach ($files as $file) {
+ $results[] = $file->getDragAndDropDictionary();
+ }
+
+ $content = array(
+ 'files' => $results,
+ );
+ return id(new AphrontAjaxResponse())->setContent($content);
+ } else {
+ $e_file = pht('Required');
+ $errors[] = pht('You must choose a file to upload.');
+ }
+ }
+
+ $form = id(new AphrontFormView())
+ ->appendChild(
+ id(new PHUIFormFileControl())
+ ->setName('filePHIDs')
+ ->setLabel(pht('Upload File'))
+ ->setAllowMultiple(true)
+ ->setError($e_file));
+
+ return $this->newDialog()
+ ->setTitle(pht('File'))
+ ->setErrors($errors)
+ ->appendForm($form)
+ ->addSubmitButton(pht('Upload'))
+ ->addCancelButton('/');
}
}
diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php
--- a/src/applications/files/storage/PhabricatorFile.php
+++ b/src/applications/files/storage/PhabricatorFile.php
@@ -851,6 +851,14 @@
return $supported;
}
+ public function getDragAndDropDictionary() {
+ return array(
+ 'id' => $this->getID(),
+ 'phid' => $this->getPHID(),
+ 'uri' => $this->getBestURI(),
+ );
+ }
+
public function instantiateStorageEngine() {
return self::buildEngine($this->getStorageEngine());
}
diff --git a/src/view/form/control/PHUIFormFileControl.php b/src/view/form/control/PHUIFormFileControl.php
new file mode 100644
--- /dev/null
+++ b/src/view/form/control/PHUIFormFileControl.php
@@ -0,0 +1,44 @@
+<?php
+
+final class PHUIFormFileControl
+ extends AphrontFormControl {
+
+ private $allowMultiple;
+
+ protected function getCustomControlClass() {
+ return 'phui-form-file-upload';
+ }
+
+ public function setAllowMultiple($allow_multiple) {
+ $this->allowMultiple = $allow_multiple;
+ return $this;
+ }
+
+ public function getAllowMultiple() {
+ return $this->allowMultiple;
+ }
+
+ protected function renderInput() {
+ $file_id = $this->getID();
+
+ Javelin::initBehavior(
+ 'phui-file-upload',
+ array(
+ 'fileInputID' => $file_id,
+ 'inputName' => $this->getName(),
+ 'uploadURI' => '/file/dropupload/',
+ 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(),
+ ));
+
+ return phutil_tag(
+ 'input',
+ array(
+ 'type' => 'file',
+ 'multiple' => $this->getAllowMultiple() ? 'multiple' : null,
+ 'name' => $this->getName().'.raw',
+ 'id' => $file_id,
+ 'disabled' => $this->getDisabled() ? 'disabled' : null,
+ ));
+ }
+
+}
diff --git a/webroot/rsrc/externals/javelin/lib/Workflow.js b/webroot/rsrc/externals/javelin/lib/Workflow.js
--- a/webroot/rsrc/externals/javelin/lib/Workflow.js
+++ b/webroot/rsrc/externals/javelin/lib/Workflow.js
@@ -25,7 +25,7 @@
this.setData(data || {});
},
- events : ['error', 'finally', 'submit'],
+ events : ['error', 'finally', 'submit', 'start'],
statics : {
_stack : [],
@@ -54,6 +54,9 @@
}
var workflow = new JX.Workflow(form.getAttribute('action'), {});
+
+ workflow._form = form;
+
workflow.setDataWithListOfPairs(pairs);
workflow.setMethod(form.getAttribute('method'));
workflow.listen('finally', function() {
@@ -137,9 +140,14 @@
data.push([button.name, button.value || true]);
var active = JX.Workflow._getActiveWorkflow();
+
+ active._form = form;
+
var e = active.invoke('submit', {form: form, data: data});
if (!e.getStopped()) {
- active._destroy();
+ // NOTE: Don't remove the current dialog yet because additional
+ // handlers may still want to access the nodes.
+
active
.setURI(form.getAttribute('action') || active.getURI())
.setDataWithListOfPairs(data)
@@ -156,7 +164,41 @@
_root : null,
_pushed : false,
_data : null,
+
+ _form: null,
+ _paused: 0,
+ _nextCallback: null,
+
+ getSourceForm: function() {
+ return this._form;
+ },
+
+ pause: function() {
+ this._paused++;
+ return this;
+ },
+
+ resume: function() {
+ if (!this._paused) {
+ JX.$E('Resuming a workflow which is not paused!');
+ }
+
+ this._paused--;
+
+ if (!this._paused) {
+ var next = this._nextCallback;
+ this._nextCallback = null;
+ if (next) {
+ next();
+ }
+ }
+
+ return this;
+ },
+
_onload : function(r) {
+ this._destroy();
+
// It is permissible to send back a falsey redirect to force a page
// reload, so we need to take this branch if the key is present.
if (r && (typeof r.redirect != 'undefined')) {
@@ -247,7 +289,19 @@
this._root = null;
}
},
+
start : function() {
+ var next = JX.bind(this, this._send);
+
+ this.pause();
+ this._nextCallback = next;
+
+ this.invoke('start', this);
+
+ this.resume();
+ },
+
+ _send: function() {
var uri = this.getURI();
var method = this.getMethod();
var r = new JX.Request(uri, JX.bind(this, this._onload));
@@ -291,6 +345,11 @@
return this;
},
+ addData: function(key, value) {
+ this._data.push([key, value]);
+ return this;
+ },
+
setDataWithListOfPairs : function(list_of_pairs) {
this._data = list_of_pairs;
return this;
diff --git a/webroot/rsrc/js/core/DragAndDropFileUpload.js b/webroot/rsrc/js/core/DragAndDropFileUpload.js
--- a/webroot/rsrc/js/core/DragAndDropFileUpload.js
+++ b/webroot/rsrc/js/core/DragAndDropFileUpload.js
@@ -155,7 +155,7 @@
var files = e.getRawEvent().dataTransfer.files;
for (var ii = 0; ii < files.length; ii++) {
- this._sendRequest(files[ii]);
+ this.sendRequest(files[ii]);
}
// Force depth to 0.
@@ -216,7 +216,7 @@
if (!spec.name) {
spec.name = 'pasted_file';
}
- this._sendRequest(spec);
+ this.sendRequest(spec);
}
}));
}
@@ -224,7 +224,7 @@
this.setIsEnabled(true);
},
- _sendRequest : function(spec) {
+ sendRequest : function(spec) {
var file = new JX.PhabricatorFileUpload()
.setRawFileObject(spec)
.setName(spec.name)
diff --git a/webroot/rsrc/js/core/TextAreaUtils.js b/webroot/rsrc/js/core/TextAreaUtils.js
--- a/webroot/rsrc/js/core/TextAreaUtils.js
+++ b/webroot/rsrc/js/core/TextAreaUtils.js
@@ -62,6 +62,26 @@
JX.TextAreaUtils.setSelectionRange(area, start, end);
},
+
+ /**
+ * Insert a reference to a given uploaded file into a textarea.
+ */
+ insertFileReference: function(area, file) {
+ var ref = '{F' + file.getID() + '}';
+
+ // If we're inserting immediately after a "}" (usually, another file
+ // reference), put some newlines before our token so that multiple file
+ // uploads get laid out more nicely.
+ var range = JX.TextAreaUtils.getSelectionRange(area);
+ var before = area.value.substring(0, range.start);
+ if (before.match(/\}$/)) {
+ ref = '\n\n' + ref;
+ }
+
+ JX.TextAreaUtils.setSelectionText(area, ref, false);
+ },
+
+
/**
* Get the document pixel positions of the beginning and end of a character
* range in a textarea.
diff --git a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js
--- a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js
+++ b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js
@@ -10,32 +10,23 @@
var target = JX.$(config.target);
- function onupload(f) {
- var ref = '{F' + f.getID() + '}';
-
- // If we're inserting immediately after a "}" (usually, another file
- // reference), put some newlines before our token so that multiple file
- // uploads get laid out more nicely.
- var range = JX.TextAreaUtils.getSelectionRange(target);
- var before = target.value.substring(0, range.start);
- if (before.match(/\}$/)) {
- ref = '\n\n' + ref;
- }
-
- JX.TextAreaUtils.setSelectionText(target, ref, false);
- }
-
if (JX.PhabricatorDragAndDropFileUpload.isSupported()) {
var drop = new JX.PhabricatorDragAndDropFileUpload(target)
.setURI(config.uri)
.setChunkThreshold(config.chunkThreshold);
+
drop.listen('didBeginDrag', function() {
JX.DOM.alterClass(target, config.activatedClass, true);
});
+
drop.listen('didEndDrag', function() {
JX.DOM.alterClass(target, config.activatedClass, false);
});
- drop.listen('didUpload', onupload);
+
+ drop.listen('didUpload', function(file) {
+ JX.TextAreaUtils.insertFileReference(target, file);
+ });
+
drop.start();
}
diff --git a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js
--- a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js
+++ b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js
@@ -194,7 +194,21 @@
.start();
break;
case 'fa-cloud-upload':
- new JX.Workflow('/file/uploaddialog/').start();
+ new JX.Workflow('/file/uploaddialog/')
+ .setHandler(function(response) {
+ var files = response.files;
+ for (var ii = 0; ii < files.length; ii++) {
+ var file = files[ii];
+
+ var upload = new JX.PhabricatorFileUpload()
+ .setID(file.id)
+ .setPHID(file.phid)
+ .setURI(file.uri);
+
+ JX.TextAreaUtils.insertFileReference(area, upload);
+ }
+ })
+ .start();
break;
case 'fa-arrows-alt':
if (edit_mode == 'fa-arrows-alt') {
diff --git a/webroot/rsrc/js/phui/behavior-phui-file-upload.js b/webroot/rsrc/js/phui/behavior-phui-file-upload.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phui/behavior-phui-file-upload.js
@@ -0,0 +1,80 @@
+/**
+ * @provides javelin-behavior-phui-file-upload
+ * @requires javelin-behavior
+ * javelin-stratcom
+ * javelin-dom
+ * phuix-dropdown-menu
+ */
+
+JX.behavior('phui-file-upload', function(config) {
+
+ function startUpload(workflow, input) {
+ var files = input.files;
+
+ if (!files || !files.length) {
+ return;
+ }
+
+ var state = {
+ workflow: workflow,
+ input: input,
+ waiting: 0,
+ phids: []
+ };
+
+ var callback = JX.bind(null, didUpload, state);
+
+ var dummy = input;
+ var uploader = new JX.PhabricatorDragAndDropFileUpload(dummy)
+ .setURI(config.uploadURI)
+ .setChunkThreshold(config.chunkThreshold);
+
+ uploader.listen('didUpload', callback);
+ uploader.start();
+
+ workflow.pause();
+ for (var ii = 0; ii < files.length; ii++) {
+ state.waiting++;
+ uploader.sendRequest(files[ii]);
+ }
+ }
+
+ function didUpload(state, file) {
+ state.phids.push(file.getPHID());
+ state.waiting--;
+
+ if (state.waiting) {
+ return;
+ }
+
+ state.workflow
+ .addData(config.inputName, state.phids.join(', '))
+ .resume();
+ }
+
+ JX.Workflow.listen('start', function(workflow) {
+ var form = workflow.getSourceForm();
+ if (!form) {
+ return;
+ }
+
+ var input;
+ try {
+ input = JX.$(config.fileInputID);
+ } catch (ex) {
+ return;
+ }
+
+ var local_form = JX.DOM.findAbove(input, 'form');
+ if (!local_form) {
+ return;
+ }
+
+ if (local_form !== form) {
+ return;
+ }
+
+ startUpload(workflow, input);
+ });
+
+});

File Metadata

Mime Type
text/plain
Expires
Thu, Mar 6, 9:23 AM (2 w, 3 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7233458
Default Alt Text
D15953.diff (15 KB)

Event Timeline