1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +01:00

Modernize file uploads

Summary:
Modernizes file uploads. In particular:

  - Adds a mobile menu, with an "Upload File" item.
  - Adds crumbs to the list view, detail view and upload view.
  - Adds "Upload File" action to crumbs.
  - Moves upload file to a separate page.
  - Removes the combined upload file + recent files page.
  - Makes upload file use a normal file control by default (works on mobile).
  - Home page, file list and file upload page are now global drop targets which accept files dropped anywhere on them. Dragging a file into the window shows a mask and an instructional message.
    - User education on this is a little weak but I think that's a big can of worms?
  - Fixes a bug where dropping multiple files into a Remarkup text area produced bad results (resolves T2190).

T879 is related, although it's specifically about Maniphest. I've declined to make global drop targets yet there because there are multiple drop targets on the page with different meanings. That UI needs updating in general.

@chad, do we have an "upload" icon (counterpart to "download")?

Test Plan: Uploaded files in Maniphest, Differential, Files, and from Home. Dragged and dropped multiple files into Differential. Used crumbs, mobile.

Reviewers: chad, btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2190

Differential Revision: https://secure.phabricator.com/D4200
This commit is contained in:
epriestley 2012-12-16 16:34:01 -08:00
parent 7e37eb4827
commit 221562b294
19 changed files with 500 additions and 448 deletions

View file

@ -56,6 +56,7 @@ $package_spec = array(
'javelin-behavior-phabricator-remarkup-assist',
'phabricator-textareautils',
'phabricator-file-upload',
'javelin-behavior-global-drag-and-drop',
),
'core.pkg.css' => array(
'phabricator-core-css',
@ -100,6 +101,7 @@ $package_spec = array(
'phabricator-side-menu-view-css',
'phabricator-crumbs-view-css',
'phabricator-object-item-list-view-css',
'global-drag-and-drop-css',
),
'differential.pkg.css' => array(

View file

@ -478,15 +478,15 @@ celerity_register_resource_map(array(
),
'/rsrc/image/sprite-apps-large-X2.png' =>
array(
'hash' => 'dffe647d8d8ddfab9b2f5f870b4e40b4',
'uri' => '/res/dffe647d/rsrc/image/sprite-apps-large-X2.png',
'hash' => 'f1218e52784088e7aabdb2744bda2cc3',
'uri' => '/res/f1218e52/rsrc/image/sprite-apps-large-X2.png',
'disk' => '/rsrc/image/sprite-apps-large-X2.png',
'type' => 'png',
),
'/rsrc/image/sprite-apps-large.png' =>
array(
'hash' => 'cc0d81cafa185c541350a9db6f3befb7',
'uri' => '/res/cc0d81ca/rsrc/image/sprite-apps-large.png',
'hash' => 'f19222adadaddd0dd7e12bcd1b1fdba9',
'uri' => '/res/f19222ad/rsrc/image/sprite-apps-large.png',
'disk' => '/rsrc/image/sprite-apps-large.png',
'type' => 'png',
),
@ -513,15 +513,15 @@ celerity_register_resource_map(array(
),
'/rsrc/image/sprite-icon-X2.png' =>
array(
'hash' => 'd4c36b33f4961bdcf63a0cc0bb4ecb1e',
'uri' => '/res/d4c36b33/rsrc/image/sprite-icon-X2.png',
'hash' => 'c9fae25bc6221922ce26517e654a18e4',
'uri' => '/res/c9fae25b/rsrc/image/sprite-icon-X2.png',
'disk' => '/rsrc/image/sprite-icon-X2.png',
'type' => 'png',
),
'/rsrc/image/sprite-icon.png' =>
array(
'hash' => 'b29f5d1d2781b6589946bd73734100f1',
'uri' => '/res/b29f5d1d/rsrc/image/sprite-icon.png',
'hash' => 'b690ea69bf5f2abe84d0a6e9ef64b03d',
'uri' => '/res/b690ea69/rsrc/image/sprite-icon.png',
'disk' => '/rsrc/image/sprite-icon.png',
'type' => 'png',
),
@ -690,7 +690,7 @@ celerity_register_resource_map(array(
),
'aphront-table-view-css' =>
array(
'uri' => '/res/732d5e1f/rsrc/css/aphront/table-view.css',
'uri' => '/res/d2cd4818/rsrc/css/aphront/table-view.css',
'type' => 'css',
'requires' =>
array(
@ -760,7 +760,7 @@ celerity_register_resource_map(array(
),
'differential-local-commits-view-css' =>
array(
'uri' => '/res/86432ba7/rsrc/css/application/differential/local-commits-view.css',
'uri' => '/res/224f3703/rsrc/css/application/differential/local-commits-view.css',
'type' => 'css',
'requires' =>
array(
@ -805,7 +805,7 @@ celerity_register_resource_map(array(
),
'differential-revision-history-css' =>
array(
'uri' => '/res/71cffe43/rsrc/css/application/differential/revision-history.css',
'uri' => '/res/d41bc64c/rsrc/css/application/differential/revision-history.css',
'type' => 'css',
'requires' =>
array(
@ -857,14 +857,14 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/css/application/diffusion/diffusion-source.css',
),
'files-css' =>
'global-drag-and-drop-css' =>
array(
'uri' => '/res/a265a77d/rsrc/css/application/files/files.css',
'uri' => '/res/f3fb4a79/rsrc/css/application/files/global-drag-and-drop.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/files/files.css',
'disk' => '/rsrc/css/application/files/global-drag-and-drop.css',
),
'herald-css' =>
array(
@ -978,20 +978,19 @@ celerity_register_resource_map(array(
),
'javelin-behavior-aphront-drag-and-drop' =>
array(
'uri' => '/res/0910fc0a/rsrc/js/application/core/behavior-drag-and-drop.js',
'uri' => '/res/84a67d72/rsrc/js/application/core/behavior-drag-and-drop.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'phabricator-drag-and-drop-file-upload',
2 => 'phabricator-drag-and-drop-file-upload',
),
'disk' => '/rsrc/js/application/core/behavior-drag-and-drop.js',
),
'javelin-behavior-aphront-drag-and-drop-textarea' =>
array(
'uri' => '/res/ad737ce4/rsrc/js/application/core/behavior-drag-and-drop-textarea.js',
'uri' => '/res/853e33b9/rsrc/js/application/core/behavior-drag-and-drop-textarea.js',
'type' => 'js',
'requires' =>
array(
@ -1363,18 +1362,19 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/application/core/behavior-fancy-datepicker.js',
),
'javelin-behavior-files-drag-and-drop' =>
'javelin-behavior-global-drag-and-drop' =>
array(
'uri' => '/res/4eb2f339/rsrc/js/application/core/behavior-files-drag-and-drop.js',
'uri' => '/res/73ae3fd1/rsrc/js/application/core/behavior-global-drag-and-drop.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-uri',
3 => 'phabricator-drag-and-drop-file-upload',
3 => 'javelin-mask',
4 => 'phabricator-drag-and-drop-file-upload',
),
'disk' => '/rsrc/js/application/core/behavior-files-drag-and-drop.js',
'disk' => '/rsrc/js/application/core/behavior-global-drag-and-drop.js',
),
'javelin-behavior-herald-rule-editor' =>
array(
@ -2473,7 +2473,7 @@ celerity_register_resource_map(array(
),
'phabricator-drag-and-drop-file-upload' =>
array(
'uri' => '/res/496110e1/rsrc/js/application/core/DragAndDropFileUpload.js',
'uri' => '/res/ce71f19a/rsrc/js/application/core/DragAndDropFileUpload.js',
'type' => 'js',
'requires' =>
array(
@ -2753,7 +2753,7 @@ celerity_register_resource_map(array(
),
'phabricator-property-list-view-css' =>
array(
'uri' => '/res/9f4d6640/rsrc/css/layout/phabricator-property-list-view.css',
'uri' => '/res/cd84ee5a/rsrc/css/layout/phabricator-property-list-view.css',
'type' => 'css',
'requires' =>
array(
@ -2810,7 +2810,7 @@ celerity_register_resource_map(array(
),
'phabricator-source-code-view-css' =>
array(
'uri' => '/res/cf0c566c/rsrc/css/layout/phabricator-source-code-view.css',
'uri' => '/res/aa04c202/rsrc/css/layout/phabricator-source-code-view.css',
'type' => 'css',
'requires' =>
array(
@ -3128,7 +3128,7 @@ celerity_register_resource_map(array(
),
'sprite-apps-large-css' =>
array(
'uri' => '/res/3629ff92/rsrc/css/sprite-apps-large.css',
'uri' => '/res/250ebd13/rsrc/css/sprite-apps-large.css',
'type' => 'css',
'requires' =>
array(
@ -3155,7 +3155,7 @@ celerity_register_resource_map(array(
),
'sprite-icon-css' =>
array(
'uri' => '/res/2e174787/rsrc/css/sprite-icon.css',
'uri' => '/res/ff841245/rsrc/css/sprite-icon.css',
'type' => 'css',
'requires' =>
array(
@ -3201,7 +3201,7 @@ celerity_register_resource_map(array(
), array(
'packages' =>
array(
'e6ad9cda' =>
'a5058778' =>
array(
'name' => 'core.pkg.css',
'symbols' =>
@ -3244,11 +3244,12 @@ celerity_register_resource_map(array(
35 => 'phabricator-side-menu-view-css',
36 => 'phabricator-crumbs-view-css',
37 => 'phabricator-object-item-list-view-css',
38 => 'global-drag-and-drop-css',
),
'uri' => '/res/pkg/e6ad9cda/core.pkg.css',
'uri' => '/res/pkg/a5058778/core.pkg.css',
'type' => 'css',
),
'ba3c323b' =>
'70c8162b' =>
array(
'name' => 'core.pkg.js',
'symbols' =>
@ -3284,8 +3285,9 @@ celerity_register_resource_map(array(
28 => 'javelin-behavior-phabricator-remarkup-assist',
29 => 'phabricator-textareautils',
30 => 'phabricator-file-upload',
31 => 'javelin-behavior-global-drag-and-drop',
),
'uri' => '/res/pkg/ba3c323b/core.pkg.js',
'uri' => '/res/pkg/70c8162b/core.pkg.js',
'type' => 'js',
),
'8edbada5' =>
@ -3300,7 +3302,7 @@ celerity_register_resource_map(array(
'uri' => '/res/pkg/8edbada5/darkconsole.pkg.js',
'type' => 'js',
),
'94cb8965' =>
'380df740' =>
array(
'name' => 'differential.pkg.css',
'symbols' =>
@ -3320,10 +3322,10 @@ celerity_register_resource_map(array(
12 => 'differential-local-commits-view-css',
13 => 'inline-comment-summary-css',
),
'uri' => '/res/pkg/94cb8965/differential.pkg.css',
'uri' => '/res/pkg/380df740/differential.pkg.css',
'type' => 'css',
),
'7ecd31fa' =>
'a8e8f2b7' =>
array(
'name' => 'differential.pkg.js',
'symbols' =>
@ -3348,7 +3350,7 @@ celerity_register_resource_map(array(
17 => 'javelin-behavior-differential-toggle-files',
18 => 'javelin-behavior-differential-user-select',
),
'uri' => '/res/pkg/7ecd31fa/differential.pkg.js',
'uri' => '/res/pkg/a8e8f2b7/differential.pkg.js',
'type' => 'js',
),
'c8ce2d88' =>
@ -3433,82 +3435,84 @@ celerity_register_resource_map(array(
'reverse' =>
array(
'aphront-attached-file-view-css' => '7839ae2d',
'aphront-crumbs-view-css' => 'e6ad9cda',
'aphront-dialog-view-css' => 'e6ad9cda',
'aphront-error-view-css' => 'e6ad9cda',
'aphront-form-view-css' => 'e6ad9cda',
'aphront-headsup-action-list-view-css' => '94cb8965',
'aphront-headsup-view-css' => 'e6ad9cda',
'aphront-list-filter-view-css' => 'e6ad9cda',
'aphront-pager-view-css' => 'e6ad9cda',
'aphront-panel-view-css' => 'e6ad9cda',
'aphront-side-nav-view-css' => 'e6ad9cda',
'aphront-table-view-css' => 'e6ad9cda',
'aphront-tokenizer-control-css' => 'e6ad9cda',
'aphront-tooltip-css' => 'e6ad9cda',
'aphront-typeahead-control-css' => 'e6ad9cda',
'differential-changeset-view-css' => '94cb8965',
'differential-core-view-css' => '94cb8965',
'differential-inline-comment-editor' => '7ecd31fa',
'differential-local-commits-view-css' => '94cb8965',
'differential-results-table-css' => '94cb8965',
'differential-revision-add-comment-css' => '94cb8965',
'differential-revision-comment-css' => '94cb8965',
'differential-revision-comment-list-css' => '94cb8965',
'differential-revision-history-css' => '94cb8965',
'differential-revision-list-css' => '94cb8965',
'differential-table-of-contents-css' => '94cb8965',
'aphront-crumbs-view-css' => 'a5058778',
'aphront-dialog-view-css' => 'a5058778',
'aphront-error-view-css' => 'a5058778',
'aphront-form-view-css' => 'a5058778',
'aphront-headsup-action-list-view-css' => '380df740',
'aphront-headsup-view-css' => 'a5058778',
'aphront-list-filter-view-css' => 'a5058778',
'aphront-pager-view-css' => 'a5058778',
'aphront-panel-view-css' => 'a5058778',
'aphront-side-nav-view-css' => 'a5058778',
'aphront-table-view-css' => 'a5058778',
'aphront-tokenizer-control-css' => 'a5058778',
'aphront-tooltip-css' => 'a5058778',
'aphront-typeahead-control-css' => 'a5058778',
'differential-changeset-view-css' => '380df740',
'differential-core-view-css' => '380df740',
'differential-inline-comment-editor' => 'a8e8f2b7',
'differential-local-commits-view-css' => '380df740',
'differential-results-table-css' => '380df740',
'differential-revision-add-comment-css' => '380df740',
'differential-revision-comment-css' => '380df740',
'differential-revision-comment-list-css' => '380df740',
'differential-revision-history-css' => '380df740',
'differential-revision-list-css' => '380df740',
'differential-table-of-contents-css' => '380df740',
'diffusion-commit-view-css' => 'c8ce2d88',
'diffusion-icons-css' => 'c8ce2d88',
'inline-comment-summary-css' => '94cb8965',
'javelin-aphlict' => 'ba3c323b',
'global-drag-and-drop-css' => 'a5058778',
'inline-comment-summary-css' => '380df740',
'javelin-aphlict' => '70c8162b',
'javelin-behavior' => 'db6d724d',
'javelin-behavior-aphlict-dropdown' => 'ba3c323b',
'javelin-behavior-aphlict-listen' => 'ba3c323b',
'javelin-behavior-aphront-basic-tokenizer' => 'ba3c323b',
'javelin-behavior-aphront-drag-and-drop' => '7ecd31fa',
'javelin-behavior-aphront-drag-and-drop-textarea' => '7ecd31fa',
'javelin-behavior-aphront-form-disable-on-submit' => 'ba3c323b',
'javelin-behavior-aphlict-dropdown' => '70c8162b',
'javelin-behavior-aphlict-listen' => '70c8162b',
'javelin-behavior-aphront-basic-tokenizer' => '70c8162b',
'javelin-behavior-aphront-drag-and-drop' => 'a8e8f2b7',
'javelin-behavior-aphront-drag-and-drop-textarea' => 'a8e8f2b7',
'javelin-behavior-aphront-form-disable-on-submit' => '70c8162b',
'javelin-behavior-audit-preview' => '5e68be89',
'javelin-behavior-dark-console' => '8edbada5',
'javelin-behavior-dark-console-ajax' => '8edbada5',
'javelin-behavior-device' => 'ba3c323b',
'javelin-behavior-differential-accept-with-errors' => '7ecd31fa',
'javelin-behavior-differential-add-reviewers-and-ccs' => '7ecd31fa',
'javelin-behavior-differential-comment-jump' => '7ecd31fa',
'javelin-behavior-differential-diff-radios' => '7ecd31fa',
'javelin-behavior-differential-dropdown-menus' => '7ecd31fa',
'javelin-behavior-differential-edit-inline-comments' => '7ecd31fa',
'javelin-behavior-differential-feedback-preview' => '7ecd31fa',
'javelin-behavior-differential-keyboard-navigation' => '7ecd31fa',
'javelin-behavior-differential-populate' => '7ecd31fa',
'javelin-behavior-differential-show-more' => '7ecd31fa',
'javelin-behavior-differential-toggle-files' => '7ecd31fa',
'javelin-behavior-differential-user-select' => '7ecd31fa',
'javelin-behavior-device' => '70c8162b',
'javelin-behavior-differential-accept-with-errors' => 'a8e8f2b7',
'javelin-behavior-differential-add-reviewers-and-ccs' => 'a8e8f2b7',
'javelin-behavior-differential-comment-jump' => 'a8e8f2b7',
'javelin-behavior-differential-diff-radios' => 'a8e8f2b7',
'javelin-behavior-differential-dropdown-menus' => 'a8e8f2b7',
'javelin-behavior-differential-edit-inline-comments' => 'a8e8f2b7',
'javelin-behavior-differential-feedback-preview' => 'a8e8f2b7',
'javelin-behavior-differential-keyboard-navigation' => 'a8e8f2b7',
'javelin-behavior-differential-populate' => 'a8e8f2b7',
'javelin-behavior-differential-show-more' => 'a8e8f2b7',
'javelin-behavior-differential-toggle-files' => 'a8e8f2b7',
'javelin-behavior-differential-user-select' => 'a8e8f2b7',
'javelin-behavior-diffusion-commit-graph' => '5e68be89',
'javelin-behavior-diffusion-pull-lastmodified' => '5e68be89',
'javelin-behavior-error-log' => '8edbada5',
'javelin-behavior-konami' => 'ba3c323b',
'javelin-behavior-lightbox-attachments' => 'ba3c323b',
'javelin-behavior-global-drag-and-drop' => '70c8162b',
'javelin-behavior-konami' => '70c8162b',
'javelin-behavior-lightbox-attachments' => '70c8162b',
'javelin-behavior-maniphest-batch-selector' => '7707de41',
'javelin-behavior-maniphest-subpriority-editor' => '7707de41',
'javelin-behavior-maniphest-transaction-controls' => '7707de41',
'javelin-behavior-maniphest-transaction-expand' => '7707de41',
'javelin-behavior-maniphest-transaction-preview' => '7707de41',
'javelin-behavior-phabricator-active-nav' => 'ba3c323b',
'javelin-behavior-phabricator-autofocus' => 'ba3c323b',
'javelin-behavior-phabricator-keyboard-shortcuts' => 'ba3c323b',
'javelin-behavior-phabricator-nav' => 'ba3c323b',
'javelin-behavior-phabricator-object-selector' => '7ecd31fa',
'javelin-behavior-phabricator-oncopy' => 'ba3c323b',
'javelin-behavior-phabricator-remarkup-assist' => 'ba3c323b',
'javelin-behavior-phabricator-search-typeahead' => 'ba3c323b',
'javelin-behavior-phabricator-tooltips' => 'ba3c323b',
'javelin-behavior-phabricator-watch-anchor' => 'ba3c323b',
'javelin-behavior-refresh-csrf' => 'ba3c323b',
'javelin-behavior-repository-crossreference' => '7ecd31fa',
'javelin-behavior-toggle-class' => 'ba3c323b',
'javelin-behavior-workflow' => 'ba3c323b',
'javelin-behavior-phabricator-active-nav' => '70c8162b',
'javelin-behavior-phabricator-autofocus' => '70c8162b',
'javelin-behavior-phabricator-keyboard-shortcuts' => '70c8162b',
'javelin-behavior-phabricator-nav' => '70c8162b',
'javelin-behavior-phabricator-object-selector' => 'a8e8f2b7',
'javelin-behavior-phabricator-oncopy' => '70c8162b',
'javelin-behavior-phabricator-remarkup-assist' => '70c8162b',
'javelin-behavior-phabricator-search-typeahead' => '70c8162b',
'javelin-behavior-phabricator-tooltips' => '70c8162b',
'javelin-behavior-phabricator-watch-anchor' => '70c8162b',
'javelin-behavior-refresh-csrf' => '70c8162b',
'javelin-behavior-repository-crossreference' => 'a8e8f2b7',
'javelin-behavior-toggle-class' => '70c8162b',
'javelin-behavior-workflow' => '70c8162b',
'javelin-dom' => 'db6d724d',
'javelin-event' => 'db6d724d',
'javelin-install' => 'db6d724d',
@ -3527,48 +3531,48 @@ celerity_register_resource_map(array(
'javelin-util' => 'db6d724d',
'javelin-vector' => 'db6d724d',
'javelin-workflow' => 'db6d724d',
'lightbox-attachment-css' => 'e6ad9cda',
'lightbox-attachment-css' => 'a5058778',
'maniphest-task-summary-css' => '7839ae2d',
'maniphest-transaction-detail-css' => '7839ae2d',
'phabricator-app-buttons-css' => 'e6ad9cda',
'phabricator-busy' => 'ba3c323b',
'phabricator-content-source-view-css' => '94cb8965',
'phabricator-core-buttons-css' => 'e6ad9cda',
'phabricator-core-css' => 'e6ad9cda',
'phabricator-crumbs-view-css' => 'e6ad9cda',
'phabricator-directory-css' => 'e6ad9cda',
'phabricator-drag-and-drop-file-upload' => '7ecd31fa',
'phabricator-dropdown-menu' => 'ba3c323b',
'phabricator-file-upload' => 'ba3c323b',
'phabricator-filetree-view-css' => 'e6ad9cda',
'phabricator-flag-css' => 'e6ad9cda',
'phabricator-form-view-css' => 'e6ad9cda',
'phabricator-header-view-css' => 'e6ad9cda',
'phabricator-jump-nav' => 'e6ad9cda',
'phabricator-keyboard-shortcut' => 'ba3c323b',
'phabricator-keyboard-shortcut-manager' => 'ba3c323b',
'phabricator-main-menu-view' => 'e6ad9cda',
'phabricator-menu-item' => 'ba3c323b',
'phabricator-nav-view-css' => 'e6ad9cda',
'phabricator-notification' => 'ba3c323b',
'phabricator-notification-css' => 'e6ad9cda',
'phabricator-notification-menu-css' => 'e6ad9cda',
'phabricator-object-item-list-view-css' => 'e6ad9cda',
'phabricator-object-selector-css' => '94cb8965',
'phabricator-paste-file-upload' => 'ba3c323b',
'phabricator-prefab' => 'ba3c323b',
'phabricator-app-buttons-css' => 'a5058778',
'phabricator-busy' => '70c8162b',
'phabricator-content-source-view-css' => '380df740',
'phabricator-core-buttons-css' => 'a5058778',
'phabricator-core-css' => 'a5058778',
'phabricator-crumbs-view-css' => 'a5058778',
'phabricator-directory-css' => 'a5058778',
'phabricator-drag-and-drop-file-upload' => 'a8e8f2b7',
'phabricator-dropdown-menu' => '70c8162b',
'phabricator-file-upload' => '70c8162b',
'phabricator-filetree-view-css' => 'a5058778',
'phabricator-flag-css' => 'a5058778',
'phabricator-form-view-css' => 'a5058778',
'phabricator-header-view-css' => 'a5058778',
'phabricator-jump-nav' => 'a5058778',
'phabricator-keyboard-shortcut' => '70c8162b',
'phabricator-keyboard-shortcut-manager' => '70c8162b',
'phabricator-main-menu-view' => 'a5058778',
'phabricator-menu-item' => '70c8162b',
'phabricator-nav-view-css' => 'a5058778',
'phabricator-notification' => '70c8162b',
'phabricator-notification-css' => 'a5058778',
'phabricator-notification-menu-css' => 'a5058778',
'phabricator-object-item-list-view-css' => 'a5058778',
'phabricator-object-selector-css' => '380df740',
'phabricator-paste-file-upload' => '70c8162b',
'phabricator-prefab' => '70c8162b',
'phabricator-project-tag-css' => '7839ae2d',
'phabricator-remarkup-css' => 'e6ad9cda',
'phabricator-shaped-request' => '7ecd31fa',
'phabricator-side-menu-view-css' => 'e6ad9cda',
'phabricator-standard-page-view' => 'e6ad9cda',
'phabricator-textareautils' => 'ba3c323b',
'phabricator-tooltip' => 'ba3c323b',
'phabricator-transaction-view-css' => 'e6ad9cda',
'sprite-apps-large-css' => 'e6ad9cda',
'sprite-gradient-css' => 'e6ad9cda',
'sprite-icon-css' => 'e6ad9cda',
'sprite-menu-css' => 'e6ad9cda',
'syntax-highlighting-css' => 'e6ad9cda',
'phabricator-remarkup-css' => 'a5058778',
'phabricator-shaped-request' => 'a8e8f2b7',
'phabricator-side-menu-view-css' => 'a5058778',
'phabricator-standard-page-view' => 'a5058778',
'phabricator-textareautils' => '70c8162b',
'phabricator-tooltip' => '70c8162b',
'phabricator-transaction-view-css' => 'a5058778',
'sprite-apps-large-css' => 'a5058778',
'sprite-gradient-css' => 'a5058778',
'sprite-icon-css' => 'a5058778',
'sprite-menu-css' => 'a5058778',
'syntax-highlighting-css' => 'a5058778',
),
));

View file

@ -788,7 +788,6 @@ phutil_register_library_map(array(
'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php',
'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php',
'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php',
'PhabricatorFileUploadView' => 'applications/files/view/PhabricatorFileUploadView.php',
'PhabricatorFilesManagementEnginesWorkflow' => 'applications/files/management/PhabricatorFilesManagementEnginesWorkflow.php',
'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php',
'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php',
@ -807,6 +806,7 @@ phutil_register_library_map(array(
'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/PhabricatorGarbageCollectorDaemon.php',
'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php',
'PhabricatorGlobalLock' => 'infrastructure/util/PhabricatorGlobalLock.php',
'PhabricatorGlobalUploadTargetView' => 'applications/files/view/PhabricatorGlobalUploadTargetView.php',
'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/PhabricatorHandleObjectSelectorDataView.php',
'PhabricatorHash' => 'infrastructure/util/PhabricatorHash.php',
'PhabricatorHeaderView' => 'view/layout/PhabricatorHeaderView.php',
@ -2070,7 +2070,6 @@ phutil_register_library_map(array(
'PhabricatorFileTransformController' => 'PhabricatorFileController',
'PhabricatorFileUploadController' => 'PhabricatorFileController',
'PhabricatorFileUploadException' => 'Exception',
'PhabricatorFileUploadView' => 'AphrontView',
'PhabricatorFilesManagementEnginesWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementWorkflow' => 'PhutilArgumentWorkflow',
@ -2086,6 +2085,7 @@ phutil_register_library_map(array(
'PhabricatorFormExample' => 'PhabricatorUIExample',
'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon',
'PhabricatorGlobalLock' => 'PhutilLock',
'PhabricatorGlobalUploadTargetView' => 'AphrontView',
'PhabricatorHeaderView' => 'AphrontView',
'PhabricatorHelpController' => 'PhabricatorController',
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',

View file

@ -76,6 +76,7 @@ final class PhabricatorDirectoryMainController
);
$nav->appendChild($content);
$nav->appendChild(new PhabricatorGlobalUploadTargetView());
return $this->buildStandardPageResponse(
$nav,
@ -491,7 +492,7 @@ final class PhabricatorDirectoryMainController
$nav_buttons[] = array(
'Upload File',
'/file/',
'/file/upload/',
'upload-file',
'Share Files');
$nav_buttons[] = array(

View file

@ -2,17 +2,43 @@
abstract class PhabricatorFileController extends PhabricatorController {
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
public function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addAction(
id(new PhabricatorMenuItemView())
->setName(pht('Upload File'))
->setIcon('create') // TODO: Get @chad to build an "upload" icon.
->setHref($this->getApplicationURI('/upload/')));
$page->setApplicationName('Files');
$page->setBaseURI('/file/');
$page->setTitle(idx($data, 'title'));
$page->setGlyph("\xE2\x87\xAA");
$page->appendChild($view);
$response = new AphrontWebpageResponse();
return $response->setContent($page->render());
return $crumbs;
}
protected function buildSideNavView() {
$menu = $this->buildMenu($for_devices = false);
return AphrontSideNavFilterView::newFromMenu($menu);
}
protected function buildApplicationMenu() {
return $this->buildMenu($for_devices = true);
}
private function buildMenu($for_devices) {
$menu = new PhabricatorMenuView();
if ($for_devices) {
$menu->newLink(pht('Upload File'), $this->getApplicationURI('/upload/'));
}
$menu->newLabel(pht('Filters'));
$menu->newLink(pht('My Files'), $this->getApplicationURI('filter/my/'))
->setKey('my');
$menu->newLink(pht('All Files'), $this->getApplicationURI('filter/all/'))
->setKey('all');
return $menu;
}
}

View file

@ -1,35 +1,20 @@
<?php
final class PhabricatorFileListController extends PhabricatorFileController {
private $filter;
private $useBasicUploader = false;
private $listAuthor;
private $listRows;
private $listRowClasses;
private function setFilter($filter) {
$this->filter = $filter;
return $this;
}
private function getFilter() {
return $this->filter;
}
private function useBasicUploader() {
return $this->getUseBasicUploader();
}
private function getUseBasicUploader() {
return $this->useBasicUploader;
}
private function setUseBasicUploader($use_basic_uploader) {
$this->useBasicUploader = $use_basic_uploader;
return $this;
}
public function willProcessRequest(array $data) {
$this->setFilter(idx($data, 'filter', 'upload'));
$this->setFilter(idx($data, 'filter', 'my'));
}
public function processRequest() {
@ -42,26 +27,13 @@ final class PhabricatorFileListController extends PhabricatorFileController {
$query = id(new PhabricatorFileQuery())
->setViewer($user);
$show_pager = true;
$show_upload = false;
switch ($this->getFilter()) {
case 'upload':
default:
$this->setUseBasicUploader($request->getExists('basic_uploader'));
$query->withAuthorPHIDs(array($user->getPHID()));
$pager->setPageSize(10);
$header = pht('Recently Uploaded Files');
$show_pager = false;
$show_upload = true;
break;
case 'my':
$query->withAuthorPHIDs(array($user->getPHID()));
$header = pht('Files You Uploaded');
break;
case 'all':
default:
$header = pht('All Files');
break;
}
@ -74,9 +46,6 @@ final class PhabricatorFileListController extends PhabricatorFileController {
$side_nav = $this->buildSideNavView();
$side_nav->selectFilter($this->getFilter());
if ($show_upload) {
$side_nav->appendChild($this->renderUploadPanel());
}
$header_view = id(new PhabricatorHeaderView())
->setHeader($header);
@ -85,13 +54,23 @@ final class PhabricatorFileListController extends PhabricatorFileController {
array(
$header_view,
$file_list,
$show_pager ? $pager : null,
$pager,
new PhabricatorGlobalUploadTargetView(),
));
$side_nav->setCrumbs(
$this
->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName($header)
->setHref($request->getRequestURI())));
return $this->buildApplicationPage(
$side_nav,
array(
'title' => 'Files',
'device' => true,
));
}
@ -141,66 +120,4 @@ final class PhabricatorFileListController extends PhabricatorFileController {
return $list_view;
}
private function buildSideNavView() {
$view = new AphrontSideNavFilterView();
$view->setBaseURI(new PhutilURI($this->getApplicationURI('/filter/')));
$view->addLabel('Files');
$view->addFilter('upload', 'Upload File');
$view->addFilter('my', 'My Files');
$view->addFilter('all', 'All Files');
return $view;
}
private function renderUploadPanel() {
$request = $this->getRequest();
$user = $request->getUser();
$limit_text = PhabricatorFileUploadView::renderUploadLimit();
if ($this->useBasicUploader()) {
$upload_panel = new PhabricatorFileUploadView();
$upload_panel->setUser($user);
} else {
require_celerity_resource('files-css');
$upload_id = celerity_generate_unique_node_id();
$panel_id = celerity_generate_unique_node_id();
$upload_panel = new AphrontPanelView();
$upload_panel->setHeader('Upload Files');
$upload_panel->setCaption($limit_text);
$upload_panel->setCreateButton('Basic Uploader',
$request->getRequestURI()->setQueryParam('basic_uploader', true)
);
$upload_panel->setWidth(AphrontPanelView::WIDTH_FULL);
$upload_panel->setID($panel_id);
$upload_panel->appendChild(
phutil_render_tag(
'div',
array(
'id' => $upload_id,
'style' => 'display: none;',
'class' => 'files-drag-and-drop',
),
''));
Javelin::initBehavior(
'files-drag-and-drop',
array(
'uri' => '/file/dropupload/',
'browseURI' => '/file/filter/my/',
'control' => $upload_id,
'target' => $panel_id,
'activatedClass' => 'aphront-panel-view-drag-and-drop',
));
}
return $upload_panel;
}
}

View file

@ -3,28 +3,114 @@
final class PhabricatorFileUploadController extends PhabricatorFileController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$e_file = true;
$errors = array();
if ($request->isFormPost()) {
$file = PhabricatorFile::newFromPHPUpload(
idx($_FILES, 'file'),
array(
'name' => $request->getStr('name'),
'authorPHID' => $user->getPHID(),
));
if (!$request->getFileExists('file')) {
$e_file = pht('Required');
$errors[] = pht('You must select a file to upload.');
} else {
$file = PhabricatorFile::newFromPHPUpload(
idx($_FILES, 'file'),
array(
'name' => $request->getStr('name'),
'authorPHID' => $user->getPHID(),
));
}
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
if (!$errors) {
return id(new AphrontRedirectResponse())->setURI($file->getViewURI());
}
}
$panel = new PhabricatorFileUploadView();
$panel->setUser($user);
$support_id = celerity_generate_unique_node_id();
$instructions = id(new AphrontFormMarkupControl())
->setControlID($support_id)
->setControlStyle('display: none')
->setValue(
'<br /><br />'.
pht(
'<strong>Drag and Drop:</strong> You can also upload files by '.
'dragging and dropping them from your desktop onto this page or '.
'the Phabricator home page.').
'<br /><br />');
return $this->buildStandardPageResponse(
array($panel),
$form = id(new AphrontFormView())
->setFlexible(true)
->setUser($user)
->setEncType('multipart/form-data')
->appendChild(
id(new AphrontFormFileControl())
->setLabel(pht('File'))
->setName('file')
->setError($e_file)
->setCaption($this->renderUploadLimit()))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($request->getStr('name'))
->setCaption('Optional file display name.'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Upload'))
->addCancelButton('/file/'))
->appendChild($instructions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Upload'))
->setHref($request->getRequestURI()));
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Upload File'));
if ($errors) {
$errors = id(new AphrontErrorView())
->setTitle('Form Errors')
->setErrors($errors);
}
$global_upload = id(new PhabricatorGlobalUploadTargetView())
->setShowIfSupportedID($support_id);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$errors,
$form,
$global_upload,
),
array(
'title' => 'Upload File',
'device' => true,
));
}
private function renderUploadLimit() {
$limit = PhabricatorEnv::getEnvConfig('storage.upload-size-limit');
$limit = phabricator_parse_bytes($limit);
if ($limit) {
$formatted = phabricator_format_bytes($limit);
return 'Maximum file size: '.phutil_escape_html($formatted);
}
$doc_href = PhabricatorEnv::getDocLink(
'article/Configuring_File_Upload_Limits.html');
$doc_link = phutil_render_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
'Configuring File Upload Limits');
return 'Upload limit is not configured, see '.$doc_link.'.';
}
}

View file

@ -1,73 +0,0 @@
<?php
final class PhabricatorFileUploadView extends AphrontView {
private $user;
private function getUser() {
return $this->user;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function render() {
$user = $this->getUser();
if (!$user) {
throw new Exception("Call setUser() before render()!");
}
$form = new AphrontFormView();
$form->setAction('/file/upload/');
$form->setUser($user);
$form
->setEncType('multipart/form-data')
->appendChild(
id(new AphrontFormFileControl())
->setLabel('File')
->setName('file')
->setError(true)
->setCaption(self::renderUploadLimit()))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Name')
->setName('name')
->setCaption('Optional file display name.'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Upload')
->addCancelButton('/file/'));
$panel = new AphrontPanelView();
$panel->setHeader('Upload File');
$panel->appendChild($form);
$panel->setWidth(AphrontPanelView::WIDTH_FULL);
return $panel->render();
}
public static function renderUploadLimit() {
$limit = PhabricatorEnv::getEnvConfig('storage.upload-size-limit');
$limit = phabricator_parse_bytes($limit);
if ($limit) {
$formatted = phabricator_format_bytes($limit);
return 'Maximum file size: '.phutil_escape_html($formatted);
}
$doc_href = PhabricatorEnv::getDocLink(
'article/Configuring_File_Upload_Limits.html');
$doc_link = phutil_render_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
'Configuring File Upload Limits');
return 'Upload limit is not configured, see '.$doc_link.'.';
}
}

View file

@ -0,0 +1,37 @@
<?php
final class PhabricatorGlobalUploadTargetView extends AphrontView {
private $showIfSupportedID;
public function setShowIfSupportedID($show_if_supported_id) {
$this->showIfSupportedID = $show_if_supported_id;
return $this;
}
public function getShowIfSupportedID() {
return $this->showIfSupportedID;
}
public function render() {
$instructions_id = celerity_generate_unique_node_id();
require_celerity_resource('global-drag-and-drop-css');
Javelin::initBehavior('global-drag-and-drop', array(
'ifSupported' => $this->showIfSupportedID,
'instructions' => $instructions_id,
'uploadURI' => '/file/dropupload/',
'browseURI' => '/file/filter/my/',
));
return phutil_render_tag(
'div',
array(
'id' => $instructions_id,
'class' => 'phabricator-global-upload-instructions',
'style' => 'display: none;',
),
pht("\xE2\x87\xAA Drop Files to Upload"));
}
}

View file

@ -11,7 +11,6 @@ class AphrontFormTextAreaControl extends AphrontFormControl {
private $height;
private $readOnly;
private $enableDragAndDropFileUploads;
private $customClass;
public function setHeight($height) {

View file

@ -32,6 +32,13 @@ final class AphrontSideNavFilterView extends AphrontView {
$this->menu = new PhabricatorMenuView();
}
public static function newFromMenu(PhabricatorMenuView $menu) {
$object = new AphrontSideNavFilterView();
$object->setBaseURI(new PhutilURI('/'));
$object->menu = $menu;
return $object;
}
public function setCrumbs(PhabricatorCrumbsView $crumbs) {
$this->crumbs = $crumbs;
return $this;
@ -128,7 +135,7 @@ final class AphrontSideNavFilterView extends AphrontView {
public function selectFilter($key, $default = null) {
$this->selectedFilter = $default;
if ($this->menu->getItem($key)) {
if ($this->menu->getItem($key) && strlen($key)) {
$this->selectedFilter = $key;
}
return $this->selectedFilter;

View file

@ -3,7 +3,6 @@
final class PhabricatorMenuView extends AphrontView {
private $items = array();
private $map = array();
private $classes = array();
public function addClass($class) {
@ -34,14 +33,6 @@ final class PhabricatorMenuView extends AphrontView {
public function addMenuItem(PhabricatorMenuItemView $item) {
$key = $item->getKey();
if ($key !== null) {
if (isset($this->map[$key])) {
throw new Exception(
"Menu contains duplicate items with key '{$key}'!");
}
$this->map[$key] = $item;
}
$this->items[] = $item;
$this->appendChild($item);
@ -49,7 +40,19 @@ final class PhabricatorMenuView extends AphrontView {
}
public function getItem($key) {
return idx($this->map, (string)$key);
$key = (string)$key;
// NOTE: We could optimize this, but need to update any map when items have
// their keys change. Since that's moderately complex, wait for a profile
// or use case.
foreach ($this->items as $item) {
if ($item->getKey() == $key) {
return $item;
}
}
return null;
}
public function getItems() {
@ -57,6 +60,18 @@ final class PhabricatorMenuView extends AphrontView {
}
public function render() {
$key_map = array();
foreach ($this->items as $item) {
$key = $item->getKey();
if ($key !== null) {
if (isset($key_map[$key])) {
throw new Exception(
"Menu contains duplicate items with key '{$key}'!");
}
$key_map[$key] = $item;
}
}
$classes = $this->classes;
$classes[] = 'phabricator-menu-view';

View file

@ -1,10 +0,0 @@
/**
* @provides files-css
*/
.files-drag-and-drop {
text-align: center;
padding: 0em 1em .5em;
font-size: 15px;
color: #666666;
}

View file

@ -0,0 +1,22 @@
/**
* @provides global-drag-and-drop-css
*/
.phabricator-global-upload-instructions {
text-align: center;
position: fixed;
font-size: 36px;
font-weight: bold;
margin: 0 10%;
width: 80%;
left: 0;
top: 30%;
padding: 18px 0;
color: #ffffff;
background: rgba(0, 0, 0, 0.8);
z-index: 8;
border-radius: 18px;
}

View file

@ -15,12 +15,19 @@ JX.install('PhabricatorDragAndDropFileUpload', {
this._node = node;
},
events : ['willUpload', 'progress', 'didUpload', 'didError'],
events : [
'didBeginDrag',
'didEndDrag',
'willUpload',
'progress',
'didUpload',
'didError'],
statics : {
isSupported : function() {
// TODO: Is there a better capability test for this? This seems okay in
// Safari, Firefox and Chrome.
return !!window.FileList;
}
},
@ -29,11 +36,17 @@ JX.install('PhabricatorDragAndDropFileUpload', {
_node : null,
_depth : 0,
_updateDepth : function(delta) {
if (this._depth == 0 && delta > 0) {
JX.log('begin: ' + this._depth + ' @ ' + delta);
this.invoke('didBeginDrag');
}
this._depth += delta;
JX.DOM.alterClass(
this._node,
this.getActivatedClass(),
(this._depth > 0));
if (this._depth == 0 && delta < 0) {
JX.log('end: ' + this._depth + ' @ ' + delta);
this.invoke('didEndDrag');
}
},
start : function() {

View file

@ -12,13 +12,29 @@ JX.behavior('aphront-drag-and-drop-textarea', function(config) {
var target = JX.$(config.target);
function onupload(f) {
JX.TextAreaUtils.setSelectionText(target, '{F' + f.getID() + '}');
var text = JX.TextAreaUtils.getSelectionText(target);
var ref = '{F' + f.getID() + '}';
// If the user has dragged and dropped multiple files, we'll get an event
// each time an upload completes. Rather than overwriting the first
// reference, append the new reference if the selected text looks like an
// existing file reference.
if (text.match(/^\{F/)) {
ref = text + "\n\n" + ref;
}
JX.TextAreaUtils.setSelectionText(target, ref);
}
if (JX.PhabricatorDragAndDropFileUpload.isSupported()) {
var drop = new JX.PhabricatorDragAndDropFileUpload(target)
.setActivatedClass(config.activatedClass)
.setURI(config.uri);
drop.listen('didBeginDrag', function(e) {
JX.DOM.alterClass(target, config.activatedClass, true);
});
drop.listen('didEndDrag', function(e) {
JX.DOM.alterClass(target, config.activatedClass, false);
});
drop.listen('didUpload', onupload);
drop.start();
}

View file

@ -2,7 +2,6 @@
* @provides javelin-behavior-aphront-drag-and-drop
* @requires javelin-behavior
* javelin-dom
* javelin-util
* phabricator-drag-and-drop-file-upload
*/
@ -23,9 +22,16 @@ JX.behavior('aphront-drag-and-drop', function(config) {
var list = JX.$(config.list);
var drop = new JX.PhabricatorDragAndDropFileUpload(JX.$(config.list))
.setActivatedClass(config.activatedClass)
.setURI(config.uri);
drop.listen('didBeginDrag', function(e) {
JX.DOM.alterClass(list, config.activatedClass, true);
});
drop.listen('didEndDrag', function(e) {
JX.DOM.alterClass(list, config.activatedClass, false);
});
drop.listen('willUpload', function(f) {
pending++;
redraw();

View file

@ -1,84 +0,0 @@
/**
* @provides javelin-behavior-files-drag-and-drop
* @requires javelin-behavior
* javelin-dom
* javelin-uri
* phabricator-drag-and-drop-file-upload
*/
JX.behavior('files-drag-and-drop', function(config) {
// The control renders hidden by default; if we don't have support for
// drag-and-drop just leave it hidden.
if (!JX.PhabricatorDragAndDropFileUpload.isSupported()) {
return;
}
var pending = 0;
var files = [];
var errors = false;
var control = JX.$(config.control);
// Show the control, since we have browser support.
control.style.display = '';
var drop = new JX.PhabricatorDragAndDropFileUpload(JX.$(config.target))
.setActivatedClass(config.activatedClass)
.setURI(config.uri);
drop.listen('willUpload', function(f) {
pending++;
redraw();
});
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(','));
// Reset so if you hit 'back' into the bfcache the page is still in a
// sensible state.
redraw();
files = [];
uri.go();
} else {
redraw();
}
});
drop.listen('didError', function(f) {
pending--;
errors = true;
redraw();
});
drop.start();
redraw();
function redraw() {
var status;
if (pending) {
status = 'Uploading ' + pending + ' files...';
} else {
var arrow = String.fromCharCode(0x21EA);
status = JX.$H(
arrow + ' <strong>Drag and Drop</strong> files here to upload them.');
}
JX.DOM.setContent(control, status);
}
});

View file

@ -0,0 +1,68 @@
/**
* @provides javelin-behavior-global-drag-and-drop
* @requires javelin-behavior
* javelin-dom
* javelin-uri
* javelin-mask
* phabricator-drag-and-drop-file-upload
*/
JX.behavior('global-drag-and-drop', function(config) {
if (!JX.PhabricatorDragAndDropFileUpload.isSupported()) {
return;
}
var pending = 0;
var files = [];
var errors = false;
if (config.ifSupported) {
JX.$(config.ifSupported).style.display = '';
}
var drop = new JX.PhabricatorDragAndDropFileUpload(document.documentElement)
.setURI(config.uploadURI);
drop.listen('didBeginDrag', function(f) {
JX.Mask.show();
JX.DOM.show(JX.$(config.instructions));
});
drop.listen('didEndDrag', function(f) {
JX.Mask.hide();
JX.DOM.hide(JX.$(config.instructions));
});
drop.listen('willUpload', function(f) {
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(','));
files = [];
uri.go();
}
});
drop.listen('didError', function(f) {
pending--;
errors = true;
});
drop.start();
});