@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+499 -447
+2
scripts/celerity_mapper.php
··· 56 56 'javelin-behavior-phabricator-remarkup-assist', 57 57 'phabricator-textareautils', 58 58 'phabricator-file-upload', 59 + 'javelin-behavior-global-drag-and-drop', 59 60 ), 60 61 'core.pkg.css' => array( 61 62 'phabricator-core-css', ··· 100 101 'phabricator-side-menu-view-css', 101 102 'phabricator-crumbs-view-css', 102 103 'phabricator-object-item-list-view-css', 104 + 'global-drag-and-drop-css', 103 105 104 106 ), 105 107 'differential.pkg.css' => array(
+141 -137
src/__celerity_resource_map__.php
··· 478 478 ), 479 479 '/rsrc/image/sprite-apps-large-X2.png' => 480 480 array( 481 - 'hash' => 'dffe647d8d8ddfab9b2f5f870b4e40b4', 482 - 'uri' => '/res/dffe647d/rsrc/image/sprite-apps-large-X2.png', 481 + 'hash' => 'f1218e52784088e7aabdb2744bda2cc3', 482 + 'uri' => '/res/f1218e52/rsrc/image/sprite-apps-large-X2.png', 483 483 'disk' => '/rsrc/image/sprite-apps-large-X2.png', 484 484 'type' => 'png', 485 485 ), 486 486 '/rsrc/image/sprite-apps-large.png' => 487 487 array( 488 - 'hash' => 'cc0d81cafa185c541350a9db6f3befb7', 489 - 'uri' => '/res/cc0d81ca/rsrc/image/sprite-apps-large.png', 488 + 'hash' => 'f19222adadaddd0dd7e12bcd1b1fdba9', 489 + 'uri' => '/res/f19222ad/rsrc/image/sprite-apps-large.png', 490 490 'disk' => '/rsrc/image/sprite-apps-large.png', 491 491 'type' => 'png', 492 492 ), ··· 513 513 ), 514 514 '/rsrc/image/sprite-icon-X2.png' => 515 515 array( 516 - 'hash' => 'd4c36b33f4961bdcf63a0cc0bb4ecb1e', 517 - 'uri' => '/res/d4c36b33/rsrc/image/sprite-icon-X2.png', 516 + 'hash' => 'c9fae25bc6221922ce26517e654a18e4', 517 + 'uri' => '/res/c9fae25b/rsrc/image/sprite-icon-X2.png', 518 518 'disk' => '/rsrc/image/sprite-icon-X2.png', 519 519 'type' => 'png', 520 520 ), 521 521 '/rsrc/image/sprite-icon.png' => 522 522 array( 523 - 'hash' => 'b29f5d1d2781b6589946bd73734100f1', 524 - 'uri' => '/res/b29f5d1d/rsrc/image/sprite-icon.png', 523 + 'hash' => 'b690ea69bf5f2abe84d0a6e9ef64b03d', 524 + 'uri' => '/res/b690ea69/rsrc/image/sprite-icon.png', 525 525 'disk' => '/rsrc/image/sprite-icon.png', 526 526 'type' => 'png', 527 527 ), ··· 690 690 ), 691 691 'aphront-table-view-css' => 692 692 array( 693 - 'uri' => '/res/732d5e1f/rsrc/css/aphront/table-view.css', 693 + 'uri' => '/res/d2cd4818/rsrc/css/aphront/table-view.css', 694 694 'type' => 'css', 695 695 'requires' => 696 696 array( ··· 760 760 ), 761 761 'differential-local-commits-view-css' => 762 762 array( 763 - 'uri' => '/res/86432ba7/rsrc/css/application/differential/local-commits-view.css', 763 + 'uri' => '/res/224f3703/rsrc/css/application/differential/local-commits-view.css', 764 764 'type' => 'css', 765 765 'requires' => 766 766 array( ··· 805 805 ), 806 806 'differential-revision-history-css' => 807 807 array( 808 - 'uri' => '/res/71cffe43/rsrc/css/application/differential/revision-history.css', 808 + 'uri' => '/res/d41bc64c/rsrc/css/application/differential/revision-history.css', 809 809 'type' => 'css', 810 810 'requires' => 811 811 array( ··· 857 857 ), 858 858 'disk' => '/rsrc/css/application/diffusion/diffusion-source.css', 859 859 ), 860 - 'files-css' => 860 + 'global-drag-and-drop-css' => 861 861 array( 862 - 'uri' => '/res/a265a77d/rsrc/css/application/files/files.css', 862 + 'uri' => '/res/f3fb4a79/rsrc/css/application/files/global-drag-and-drop.css', 863 863 'type' => 'css', 864 864 'requires' => 865 865 array( 866 866 ), 867 - 'disk' => '/rsrc/css/application/files/files.css', 867 + 'disk' => '/rsrc/css/application/files/global-drag-and-drop.css', 868 868 ), 869 869 'herald-css' => 870 870 array( ··· 978 978 ), 979 979 'javelin-behavior-aphront-drag-and-drop' => 980 980 array( 981 - 'uri' => '/res/0910fc0a/rsrc/js/application/core/behavior-drag-and-drop.js', 981 + 'uri' => '/res/84a67d72/rsrc/js/application/core/behavior-drag-and-drop.js', 982 982 'type' => 'js', 983 983 'requires' => 984 984 array( 985 985 0 => 'javelin-behavior', 986 986 1 => 'javelin-dom', 987 - 2 => 'javelin-util', 988 - 3 => 'phabricator-drag-and-drop-file-upload', 987 + 2 => 'phabricator-drag-and-drop-file-upload', 989 988 ), 990 989 'disk' => '/rsrc/js/application/core/behavior-drag-and-drop.js', 991 990 ), 992 991 'javelin-behavior-aphront-drag-and-drop-textarea' => 993 992 array( 994 - 'uri' => '/res/ad737ce4/rsrc/js/application/core/behavior-drag-and-drop-textarea.js', 993 + 'uri' => '/res/853e33b9/rsrc/js/application/core/behavior-drag-and-drop-textarea.js', 995 994 'type' => 'js', 996 995 'requires' => 997 996 array( ··· 1363 1362 ), 1364 1363 'disk' => '/rsrc/js/application/core/behavior-fancy-datepicker.js', 1365 1364 ), 1366 - 'javelin-behavior-files-drag-and-drop' => 1365 + 'javelin-behavior-global-drag-and-drop' => 1367 1366 array( 1368 - 'uri' => '/res/4eb2f339/rsrc/js/application/core/behavior-files-drag-and-drop.js', 1367 + 'uri' => '/res/73ae3fd1/rsrc/js/application/core/behavior-global-drag-and-drop.js', 1369 1368 'type' => 'js', 1370 1369 'requires' => 1371 1370 array( 1372 1371 0 => 'javelin-behavior', 1373 1372 1 => 'javelin-dom', 1374 1373 2 => 'javelin-uri', 1375 - 3 => 'phabricator-drag-and-drop-file-upload', 1374 + 3 => 'javelin-mask', 1375 + 4 => 'phabricator-drag-and-drop-file-upload', 1376 1376 ), 1377 - 'disk' => '/rsrc/js/application/core/behavior-files-drag-and-drop.js', 1377 + 'disk' => '/rsrc/js/application/core/behavior-global-drag-and-drop.js', 1378 1378 ), 1379 1379 'javelin-behavior-herald-rule-editor' => 1380 1380 array( ··· 2473 2473 ), 2474 2474 'phabricator-drag-and-drop-file-upload' => 2475 2475 array( 2476 - 'uri' => '/res/496110e1/rsrc/js/application/core/DragAndDropFileUpload.js', 2476 + 'uri' => '/res/ce71f19a/rsrc/js/application/core/DragAndDropFileUpload.js', 2477 2477 'type' => 'js', 2478 2478 'requires' => 2479 2479 array( ··· 2753 2753 ), 2754 2754 'phabricator-property-list-view-css' => 2755 2755 array( 2756 - 'uri' => '/res/9f4d6640/rsrc/css/layout/phabricator-property-list-view.css', 2756 + 'uri' => '/res/cd84ee5a/rsrc/css/layout/phabricator-property-list-view.css', 2757 2757 'type' => 'css', 2758 2758 'requires' => 2759 2759 array( ··· 2810 2810 ), 2811 2811 'phabricator-source-code-view-css' => 2812 2812 array( 2813 - 'uri' => '/res/cf0c566c/rsrc/css/layout/phabricator-source-code-view.css', 2813 + 'uri' => '/res/aa04c202/rsrc/css/layout/phabricator-source-code-view.css', 2814 2814 'type' => 'css', 2815 2815 'requires' => 2816 2816 array( ··· 3128 3128 ), 3129 3129 'sprite-apps-large-css' => 3130 3130 array( 3131 - 'uri' => '/res/3629ff92/rsrc/css/sprite-apps-large.css', 3131 + 'uri' => '/res/250ebd13/rsrc/css/sprite-apps-large.css', 3132 3132 'type' => 'css', 3133 3133 'requires' => 3134 3134 array( ··· 3155 3155 ), 3156 3156 'sprite-icon-css' => 3157 3157 array( 3158 - 'uri' => '/res/2e174787/rsrc/css/sprite-icon.css', 3158 + 'uri' => '/res/ff841245/rsrc/css/sprite-icon.css', 3159 3159 'type' => 'css', 3160 3160 'requires' => 3161 3161 array( ··· 3201 3201 ), array( 3202 3202 'packages' => 3203 3203 array( 3204 - 'e6ad9cda' => 3204 + 'a5058778' => 3205 3205 array( 3206 3206 'name' => 'core.pkg.css', 3207 3207 'symbols' => ··· 3244 3244 35 => 'phabricator-side-menu-view-css', 3245 3245 36 => 'phabricator-crumbs-view-css', 3246 3246 37 => 'phabricator-object-item-list-view-css', 3247 + 38 => 'global-drag-and-drop-css', 3247 3248 ), 3248 - 'uri' => '/res/pkg/e6ad9cda/core.pkg.css', 3249 + 'uri' => '/res/pkg/a5058778/core.pkg.css', 3249 3250 'type' => 'css', 3250 3251 ), 3251 - 'ba3c323b' => 3252 + '70c8162b' => 3252 3253 array( 3253 3254 'name' => 'core.pkg.js', 3254 3255 'symbols' => ··· 3284 3285 28 => 'javelin-behavior-phabricator-remarkup-assist', 3285 3286 29 => 'phabricator-textareautils', 3286 3287 30 => 'phabricator-file-upload', 3288 + 31 => 'javelin-behavior-global-drag-and-drop', 3287 3289 ), 3288 - 'uri' => '/res/pkg/ba3c323b/core.pkg.js', 3290 + 'uri' => '/res/pkg/70c8162b/core.pkg.js', 3289 3291 'type' => 'js', 3290 3292 ), 3291 3293 '8edbada5' => ··· 3300 3302 'uri' => '/res/pkg/8edbada5/darkconsole.pkg.js', 3301 3303 'type' => 'js', 3302 3304 ), 3303 - '94cb8965' => 3305 + '380df740' => 3304 3306 array( 3305 3307 'name' => 'differential.pkg.css', 3306 3308 'symbols' => ··· 3320 3322 12 => 'differential-local-commits-view-css', 3321 3323 13 => 'inline-comment-summary-css', 3322 3324 ), 3323 - 'uri' => '/res/pkg/94cb8965/differential.pkg.css', 3325 + 'uri' => '/res/pkg/380df740/differential.pkg.css', 3324 3326 'type' => 'css', 3325 3327 ), 3326 - '7ecd31fa' => 3328 + 'a8e8f2b7' => 3327 3329 array( 3328 3330 'name' => 'differential.pkg.js', 3329 3331 'symbols' => ··· 3348 3350 17 => 'javelin-behavior-differential-toggle-files', 3349 3351 18 => 'javelin-behavior-differential-user-select', 3350 3352 ), 3351 - 'uri' => '/res/pkg/7ecd31fa/differential.pkg.js', 3353 + 'uri' => '/res/pkg/a8e8f2b7/differential.pkg.js', 3352 3354 'type' => 'js', 3353 3355 ), 3354 3356 'c8ce2d88' => ··· 3433 3435 'reverse' => 3434 3436 array( 3435 3437 'aphront-attached-file-view-css' => '7839ae2d', 3436 - 'aphront-crumbs-view-css' => 'e6ad9cda', 3437 - 'aphront-dialog-view-css' => 'e6ad9cda', 3438 - 'aphront-error-view-css' => 'e6ad9cda', 3439 - 'aphront-form-view-css' => 'e6ad9cda', 3440 - 'aphront-headsup-action-list-view-css' => '94cb8965', 3441 - 'aphront-headsup-view-css' => 'e6ad9cda', 3442 - 'aphront-list-filter-view-css' => 'e6ad9cda', 3443 - 'aphront-pager-view-css' => 'e6ad9cda', 3444 - 'aphront-panel-view-css' => 'e6ad9cda', 3445 - 'aphront-side-nav-view-css' => 'e6ad9cda', 3446 - 'aphront-table-view-css' => 'e6ad9cda', 3447 - 'aphront-tokenizer-control-css' => 'e6ad9cda', 3448 - 'aphront-tooltip-css' => 'e6ad9cda', 3449 - 'aphront-typeahead-control-css' => 'e6ad9cda', 3450 - 'differential-changeset-view-css' => '94cb8965', 3451 - 'differential-core-view-css' => '94cb8965', 3452 - 'differential-inline-comment-editor' => '7ecd31fa', 3453 - 'differential-local-commits-view-css' => '94cb8965', 3454 - 'differential-results-table-css' => '94cb8965', 3455 - 'differential-revision-add-comment-css' => '94cb8965', 3456 - 'differential-revision-comment-css' => '94cb8965', 3457 - 'differential-revision-comment-list-css' => '94cb8965', 3458 - 'differential-revision-history-css' => '94cb8965', 3459 - 'differential-revision-list-css' => '94cb8965', 3460 - 'differential-table-of-contents-css' => '94cb8965', 3438 + 'aphront-crumbs-view-css' => 'a5058778', 3439 + 'aphront-dialog-view-css' => 'a5058778', 3440 + 'aphront-error-view-css' => 'a5058778', 3441 + 'aphront-form-view-css' => 'a5058778', 3442 + 'aphront-headsup-action-list-view-css' => '380df740', 3443 + 'aphront-headsup-view-css' => 'a5058778', 3444 + 'aphront-list-filter-view-css' => 'a5058778', 3445 + 'aphront-pager-view-css' => 'a5058778', 3446 + 'aphront-panel-view-css' => 'a5058778', 3447 + 'aphront-side-nav-view-css' => 'a5058778', 3448 + 'aphront-table-view-css' => 'a5058778', 3449 + 'aphront-tokenizer-control-css' => 'a5058778', 3450 + 'aphront-tooltip-css' => 'a5058778', 3451 + 'aphront-typeahead-control-css' => 'a5058778', 3452 + 'differential-changeset-view-css' => '380df740', 3453 + 'differential-core-view-css' => '380df740', 3454 + 'differential-inline-comment-editor' => 'a8e8f2b7', 3455 + 'differential-local-commits-view-css' => '380df740', 3456 + 'differential-results-table-css' => '380df740', 3457 + 'differential-revision-add-comment-css' => '380df740', 3458 + 'differential-revision-comment-css' => '380df740', 3459 + 'differential-revision-comment-list-css' => '380df740', 3460 + 'differential-revision-history-css' => '380df740', 3461 + 'differential-revision-list-css' => '380df740', 3462 + 'differential-table-of-contents-css' => '380df740', 3461 3463 'diffusion-commit-view-css' => 'c8ce2d88', 3462 3464 'diffusion-icons-css' => 'c8ce2d88', 3463 - 'inline-comment-summary-css' => '94cb8965', 3464 - 'javelin-aphlict' => 'ba3c323b', 3465 + 'global-drag-and-drop-css' => 'a5058778', 3466 + 'inline-comment-summary-css' => '380df740', 3467 + 'javelin-aphlict' => '70c8162b', 3465 3468 'javelin-behavior' => 'db6d724d', 3466 - 'javelin-behavior-aphlict-dropdown' => 'ba3c323b', 3467 - 'javelin-behavior-aphlict-listen' => 'ba3c323b', 3468 - 'javelin-behavior-aphront-basic-tokenizer' => 'ba3c323b', 3469 - 'javelin-behavior-aphront-drag-and-drop' => '7ecd31fa', 3470 - 'javelin-behavior-aphront-drag-and-drop-textarea' => '7ecd31fa', 3471 - 'javelin-behavior-aphront-form-disable-on-submit' => 'ba3c323b', 3469 + 'javelin-behavior-aphlict-dropdown' => '70c8162b', 3470 + 'javelin-behavior-aphlict-listen' => '70c8162b', 3471 + 'javelin-behavior-aphront-basic-tokenizer' => '70c8162b', 3472 + 'javelin-behavior-aphront-drag-and-drop' => 'a8e8f2b7', 3473 + 'javelin-behavior-aphront-drag-and-drop-textarea' => 'a8e8f2b7', 3474 + 'javelin-behavior-aphront-form-disable-on-submit' => '70c8162b', 3472 3475 'javelin-behavior-audit-preview' => '5e68be89', 3473 3476 'javelin-behavior-dark-console' => '8edbada5', 3474 3477 'javelin-behavior-dark-console-ajax' => '8edbada5', 3475 - 'javelin-behavior-device' => 'ba3c323b', 3476 - 'javelin-behavior-differential-accept-with-errors' => '7ecd31fa', 3477 - 'javelin-behavior-differential-add-reviewers-and-ccs' => '7ecd31fa', 3478 - 'javelin-behavior-differential-comment-jump' => '7ecd31fa', 3479 - 'javelin-behavior-differential-diff-radios' => '7ecd31fa', 3480 - 'javelin-behavior-differential-dropdown-menus' => '7ecd31fa', 3481 - 'javelin-behavior-differential-edit-inline-comments' => '7ecd31fa', 3482 - 'javelin-behavior-differential-feedback-preview' => '7ecd31fa', 3483 - 'javelin-behavior-differential-keyboard-navigation' => '7ecd31fa', 3484 - 'javelin-behavior-differential-populate' => '7ecd31fa', 3485 - 'javelin-behavior-differential-show-more' => '7ecd31fa', 3486 - 'javelin-behavior-differential-toggle-files' => '7ecd31fa', 3487 - 'javelin-behavior-differential-user-select' => '7ecd31fa', 3478 + 'javelin-behavior-device' => '70c8162b', 3479 + 'javelin-behavior-differential-accept-with-errors' => 'a8e8f2b7', 3480 + 'javelin-behavior-differential-add-reviewers-and-ccs' => 'a8e8f2b7', 3481 + 'javelin-behavior-differential-comment-jump' => 'a8e8f2b7', 3482 + 'javelin-behavior-differential-diff-radios' => 'a8e8f2b7', 3483 + 'javelin-behavior-differential-dropdown-menus' => 'a8e8f2b7', 3484 + 'javelin-behavior-differential-edit-inline-comments' => 'a8e8f2b7', 3485 + 'javelin-behavior-differential-feedback-preview' => 'a8e8f2b7', 3486 + 'javelin-behavior-differential-keyboard-navigation' => 'a8e8f2b7', 3487 + 'javelin-behavior-differential-populate' => 'a8e8f2b7', 3488 + 'javelin-behavior-differential-show-more' => 'a8e8f2b7', 3489 + 'javelin-behavior-differential-toggle-files' => 'a8e8f2b7', 3490 + 'javelin-behavior-differential-user-select' => 'a8e8f2b7', 3488 3491 'javelin-behavior-diffusion-commit-graph' => '5e68be89', 3489 3492 'javelin-behavior-diffusion-pull-lastmodified' => '5e68be89', 3490 3493 'javelin-behavior-error-log' => '8edbada5', 3491 - 'javelin-behavior-konami' => 'ba3c323b', 3492 - 'javelin-behavior-lightbox-attachments' => 'ba3c323b', 3494 + 'javelin-behavior-global-drag-and-drop' => '70c8162b', 3495 + 'javelin-behavior-konami' => '70c8162b', 3496 + 'javelin-behavior-lightbox-attachments' => '70c8162b', 3493 3497 'javelin-behavior-maniphest-batch-selector' => '7707de41', 3494 3498 'javelin-behavior-maniphest-subpriority-editor' => '7707de41', 3495 3499 'javelin-behavior-maniphest-transaction-controls' => '7707de41', 3496 3500 'javelin-behavior-maniphest-transaction-expand' => '7707de41', 3497 3501 'javelin-behavior-maniphest-transaction-preview' => '7707de41', 3498 - 'javelin-behavior-phabricator-active-nav' => 'ba3c323b', 3499 - 'javelin-behavior-phabricator-autofocus' => 'ba3c323b', 3500 - 'javelin-behavior-phabricator-keyboard-shortcuts' => 'ba3c323b', 3501 - 'javelin-behavior-phabricator-nav' => 'ba3c323b', 3502 - 'javelin-behavior-phabricator-object-selector' => '7ecd31fa', 3503 - 'javelin-behavior-phabricator-oncopy' => 'ba3c323b', 3504 - 'javelin-behavior-phabricator-remarkup-assist' => 'ba3c323b', 3505 - 'javelin-behavior-phabricator-search-typeahead' => 'ba3c323b', 3506 - 'javelin-behavior-phabricator-tooltips' => 'ba3c323b', 3507 - 'javelin-behavior-phabricator-watch-anchor' => 'ba3c323b', 3508 - 'javelin-behavior-refresh-csrf' => 'ba3c323b', 3509 - 'javelin-behavior-repository-crossreference' => '7ecd31fa', 3510 - 'javelin-behavior-toggle-class' => 'ba3c323b', 3511 - 'javelin-behavior-workflow' => 'ba3c323b', 3502 + 'javelin-behavior-phabricator-active-nav' => '70c8162b', 3503 + 'javelin-behavior-phabricator-autofocus' => '70c8162b', 3504 + 'javelin-behavior-phabricator-keyboard-shortcuts' => '70c8162b', 3505 + 'javelin-behavior-phabricator-nav' => '70c8162b', 3506 + 'javelin-behavior-phabricator-object-selector' => 'a8e8f2b7', 3507 + 'javelin-behavior-phabricator-oncopy' => '70c8162b', 3508 + 'javelin-behavior-phabricator-remarkup-assist' => '70c8162b', 3509 + 'javelin-behavior-phabricator-search-typeahead' => '70c8162b', 3510 + 'javelin-behavior-phabricator-tooltips' => '70c8162b', 3511 + 'javelin-behavior-phabricator-watch-anchor' => '70c8162b', 3512 + 'javelin-behavior-refresh-csrf' => '70c8162b', 3513 + 'javelin-behavior-repository-crossreference' => 'a8e8f2b7', 3514 + 'javelin-behavior-toggle-class' => '70c8162b', 3515 + 'javelin-behavior-workflow' => '70c8162b', 3512 3516 'javelin-dom' => 'db6d724d', 3513 3517 'javelin-event' => 'db6d724d', 3514 3518 'javelin-install' => 'db6d724d', ··· 3527 3531 'javelin-util' => 'db6d724d', 3528 3532 'javelin-vector' => 'db6d724d', 3529 3533 'javelin-workflow' => 'db6d724d', 3530 - 'lightbox-attachment-css' => 'e6ad9cda', 3534 + 'lightbox-attachment-css' => 'a5058778', 3531 3535 'maniphest-task-summary-css' => '7839ae2d', 3532 3536 'maniphest-transaction-detail-css' => '7839ae2d', 3533 - 'phabricator-app-buttons-css' => 'e6ad9cda', 3534 - 'phabricator-busy' => 'ba3c323b', 3535 - 'phabricator-content-source-view-css' => '94cb8965', 3536 - 'phabricator-core-buttons-css' => 'e6ad9cda', 3537 - 'phabricator-core-css' => 'e6ad9cda', 3538 - 'phabricator-crumbs-view-css' => 'e6ad9cda', 3539 - 'phabricator-directory-css' => 'e6ad9cda', 3540 - 'phabricator-drag-and-drop-file-upload' => '7ecd31fa', 3541 - 'phabricator-dropdown-menu' => 'ba3c323b', 3542 - 'phabricator-file-upload' => 'ba3c323b', 3543 - 'phabricator-filetree-view-css' => 'e6ad9cda', 3544 - 'phabricator-flag-css' => 'e6ad9cda', 3545 - 'phabricator-form-view-css' => 'e6ad9cda', 3546 - 'phabricator-header-view-css' => 'e6ad9cda', 3547 - 'phabricator-jump-nav' => 'e6ad9cda', 3548 - 'phabricator-keyboard-shortcut' => 'ba3c323b', 3549 - 'phabricator-keyboard-shortcut-manager' => 'ba3c323b', 3550 - 'phabricator-main-menu-view' => 'e6ad9cda', 3551 - 'phabricator-menu-item' => 'ba3c323b', 3552 - 'phabricator-nav-view-css' => 'e6ad9cda', 3553 - 'phabricator-notification' => 'ba3c323b', 3554 - 'phabricator-notification-css' => 'e6ad9cda', 3555 - 'phabricator-notification-menu-css' => 'e6ad9cda', 3556 - 'phabricator-object-item-list-view-css' => 'e6ad9cda', 3557 - 'phabricator-object-selector-css' => '94cb8965', 3558 - 'phabricator-paste-file-upload' => 'ba3c323b', 3559 - 'phabricator-prefab' => 'ba3c323b', 3537 + 'phabricator-app-buttons-css' => 'a5058778', 3538 + 'phabricator-busy' => '70c8162b', 3539 + 'phabricator-content-source-view-css' => '380df740', 3540 + 'phabricator-core-buttons-css' => 'a5058778', 3541 + 'phabricator-core-css' => 'a5058778', 3542 + 'phabricator-crumbs-view-css' => 'a5058778', 3543 + 'phabricator-directory-css' => 'a5058778', 3544 + 'phabricator-drag-and-drop-file-upload' => 'a8e8f2b7', 3545 + 'phabricator-dropdown-menu' => '70c8162b', 3546 + 'phabricator-file-upload' => '70c8162b', 3547 + 'phabricator-filetree-view-css' => 'a5058778', 3548 + 'phabricator-flag-css' => 'a5058778', 3549 + 'phabricator-form-view-css' => 'a5058778', 3550 + 'phabricator-header-view-css' => 'a5058778', 3551 + 'phabricator-jump-nav' => 'a5058778', 3552 + 'phabricator-keyboard-shortcut' => '70c8162b', 3553 + 'phabricator-keyboard-shortcut-manager' => '70c8162b', 3554 + 'phabricator-main-menu-view' => 'a5058778', 3555 + 'phabricator-menu-item' => '70c8162b', 3556 + 'phabricator-nav-view-css' => 'a5058778', 3557 + 'phabricator-notification' => '70c8162b', 3558 + 'phabricator-notification-css' => 'a5058778', 3559 + 'phabricator-notification-menu-css' => 'a5058778', 3560 + 'phabricator-object-item-list-view-css' => 'a5058778', 3561 + 'phabricator-object-selector-css' => '380df740', 3562 + 'phabricator-paste-file-upload' => '70c8162b', 3563 + 'phabricator-prefab' => '70c8162b', 3560 3564 'phabricator-project-tag-css' => '7839ae2d', 3561 - 'phabricator-remarkup-css' => 'e6ad9cda', 3562 - 'phabricator-shaped-request' => '7ecd31fa', 3563 - 'phabricator-side-menu-view-css' => 'e6ad9cda', 3564 - 'phabricator-standard-page-view' => 'e6ad9cda', 3565 - 'phabricator-textareautils' => 'ba3c323b', 3566 - 'phabricator-tooltip' => 'ba3c323b', 3567 - 'phabricator-transaction-view-css' => 'e6ad9cda', 3568 - 'sprite-apps-large-css' => 'e6ad9cda', 3569 - 'sprite-gradient-css' => 'e6ad9cda', 3570 - 'sprite-icon-css' => 'e6ad9cda', 3571 - 'sprite-menu-css' => 'e6ad9cda', 3572 - 'syntax-highlighting-css' => 'e6ad9cda', 3565 + 'phabricator-remarkup-css' => 'a5058778', 3566 + 'phabricator-shaped-request' => 'a8e8f2b7', 3567 + 'phabricator-side-menu-view-css' => 'a5058778', 3568 + 'phabricator-standard-page-view' => 'a5058778', 3569 + 'phabricator-textareautils' => '70c8162b', 3570 + 'phabricator-tooltip' => '70c8162b', 3571 + 'phabricator-transaction-view-css' => 'a5058778', 3572 + 'sprite-apps-large-css' => 'a5058778', 3573 + 'sprite-gradient-css' => 'a5058778', 3574 + 'sprite-icon-css' => 'a5058778', 3575 + 'sprite-menu-css' => 'a5058778', 3576 + 'syntax-highlighting-css' => 'a5058778', 3573 3577 ), 3574 3578 ));
+2 -2
src/__phutil_library_map__.php
··· 788 788 'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php', 789 789 'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php', 790 790 'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php', 791 - 'PhabricatorFileUploadView' => 'applications/files/view/PhabricatorFileUploadView.php', 792 791 'PhabricatorFilesManagementEnginesWorkflow' => 'applications/files/management/PhabricatorFilesManagementEnginesWorkflow.php', 793 792 'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php', 794 793 'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php', ··· 807 806 'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/PhabricatorGarbageCollectorDaemon.php', 808 807 'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php', 809 808 'PhabricatorGlobalLock' => 'infrastructure/util/PhabricatorGlobalLock.php', 809 + 'PhabricatorGlobalUploadTargetView' => 'applications/files/view/PhabricatorGlobalUploadTargetView.php', 810 810 'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/PhabricatorHandleObjectSelectorDataView.php', 811 811 'PhabricatorHash' => 'infrastructure/util/PhabricatorHash.php', 812 812 'PhabricatorHeaderView' => 'view/layout/PhabricatorHeaderView.php', ··· 2070 2070 'PhabricatorFileTransformController' => 'PhabricatorFileController', 2071 2071 'PhabricatorFileUploadController' => 'PhabricatorFileController', 2072 2072 'PhabricatorFileUploadException' => 'Exception', 2073 - 'PhabricatorFileUploadView' => 'AphrontView', 2074 2073 'PhabricatorFilesManagementEnginesWorkflow' => 'PhabricatorFilesManagementWorkflow', 2075 2074 'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow', 2076 2075 'PhabricatorFilesManagementWorkflow' => 'PhutilArgumentWorkflow', ··· 2086 2085 'PhabricatorFormExample' => 'PhabricatorUIExample', 2087 2086 'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon', 2088 2087 'PhabricatorGlobalLock' => 'PhutilLock', 2088 + 'PhabricatorGlobalUploadTargetView' => 'AphrontView', 2089 2089 'PhabricatorHeaderView' => 'AphrontView', 2090 2090 'PhabricatorHelpController' => 'PhabricatorController', 2091 2091 'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
+2 -1
src/applications/directory/controller/PhabricatorDirectoryMainController.php
··· 76 76 ); 77 77 78 78 $nav->appendChild($content); 79 + $nav->appendChild(new PhabricatorGlobalUploadTargetView()); 79 80 80 81 return $this->buildStandardPageResponse( 81 82 $nav, ··· 491 492 492 493 $nav_buttons[] = array( 493 494 'Upload File', 494 - '/file/', 495 + '/file/upload/', 495 496 'upload-file', 496 497 'Share Files'); 497 498 $nav_buttons[] = array(
+35 -9
src/applications/files/controller/PhabricatorFileController.php
··· 2 2 3 3 abstract class PhabricatorFileController extends PhabricatorController { 4 4 5 - public function buildStandardPageResponse($view, array $data) { 6 - $page = $this->buildStandardPageView(); 5 + public function buildApplicationCrumbs() { 6 + $crumbs = parent::buildApplicationCrumbs(); 7 + $crumbs->addAction( 8 + id(new PhabricatorMenuItemView()) 9 + ->setName(pht('Upload File')) 10 + ->setIcon('create') // TODO: Get @chad to build an "upload" icon. 11 + ->setHref($this->getApplicationURI('/upload/'))); 7 12 8 - $page->setApplicationName('Files'); 9 - $page->setBaseURI('/file/'); 10 - $page->setTitle(idx($data, 'title')); 11 - $page->setGlyph("\xE2\x87\xAA"); 12 - $page->appendChild($view); 13 + return $crumbs; 14 + } 13 15 14 - $response = new AphrontWebpageResponse(); 15 - return $response->setContent($page->render()); 16 + protected function buildSideNavView() { 17 + $menu = $this->buildMenu($for_devices = false); 18 + return AphrontSideNavFilterView::newFromMenu($menu); 16 19 } 20 + 21 + protected function buildApplicationMenu() { 22 + return $this->buildMenu($for_devices = true); 23 + } 24 + 25 + private function buildMenu($for_devices) { 26 + $menu = new PhabricatorMenuView(); 27 + 28 + if ($for_devices) { 29 + $menu->newLink(pht('Upload File'), $this->getApplicationURI('/upload/')); 30 + } 31 + 32 + $menu->newLabel(pht('Filters')); 33 + 34 + $menu->newLink(pht('My Files'), $this->getApplicationURI('filter/my/')) 35 + ->setKey('my'); 36 + 37 + $menu->newLink(pht('All Files'), $this->getApplicationURI('filter/all/')) 38 + ->setKey('all'); 39 + 40 + return $menu; 41 + } 42 + 17 43 18 44 }
+15 -98
src/applications/files/controller/PhabricatorFileListController.php
··· 1 1 <?php 2 2 3 3 final class PhabricatorFileListController extends PhabricatorFileController { 4 - private $filter; 5 4 6 - private $useBasicUploader = false; 7 - 8 - private $listAuthor; 9 - private $listRows; 10 - private $listRowClasses; 5 + private $filter; 11 6 12 7 private function setFilter($filter) { 13 8 $this->filter = $filter; 14 9 return $this; 15 10 } 11 + 16 12 private function getFilter() { 17 13 return $this->filter; 18 14 } 19 15 20 - private function useBasicUploader() { 21 - return $this->getUseBasicUploader(); 22 - } 23 - private function getUseBasicUploader() { 24 - return $this->useBasicUploader; 25 - } 26 - private function setUseBasicUploader($use_basic_uploader) { 27 - $this->useBasicUploader = $use_basic_uploader; 28 - return $this; 29 - } 30 - 31 16 public function willProcessRequest(array $data) { 32 - $this->setFilter(idx($data, 'filter', 'upload')); 17 + $this->setFilter(idx($data, 'filter', 'my')); 33 18 } 34 19 35 20 public function processRequest() { ··· 42 27 $query = id(new PhabricatorFileQuery()) 43 28 ->setViewer($user); 44 29 45 - $show_pager = true; 46 - $show_upload = false; 47 - 48 30 switch ($this->getFilter()) { 49 - case 'upload': 50 - default: 51 - $this->setUseBasicUploader($request->getExists('basic_uploader')); 52 - 53 - $query->withAuthorPHIDs(array($user->getPHID())); 54 - $pager->setPageSize(10); 55 - 56 - $header = pht('Recently Uploaded Files'); 57 - $show_pager = false; 58 - $show_upload = true; 59 - break; 60 31 case 'my': 61 32 $query->withAuthorPHIDs(array($user->getPHID())); 62 33 $header = pht('Files You Uploaded'); 63 34 break; 64 35 case 'all': 36 + default: 65 37 $header = pht('All Files'); 66 38 break; 67 39 } ··· 74 46 75 47 $side_nav = $this->buildSideNavView(); 76 48 $side_nav->selectFilter($this->getFilter()); 77 - if ($show_upload) { 78 - $side_nav->appendChild($this->renderUploadPanel()); 79 - } 80 49 81 50 $header_view = id(new PhabricatorHeaderView()) 82 51 ->setHeader($header); ··· 85 54 array( 86 55 $header_view, 87 56 $file_list, 88 - $show_pager ? $pager : null, 57 + $pager, 58 + new PhabricatorGlobalUploadTargetView(), 89 59 )); 90 60 61 + $side_nav->setCrumbs( 62 + $this 63 + ->buildApplicationCrumbs() 64 + ->addCrumb( 65 + id(new PhabricatorCrumbView()) 66 + ->setName($header) 67 + ->setHref($request->getRequestURI()))); 68 + 91 69 return $this->buildApplicationPage( 92 70 $side_nav, 93 71 array( 94 72 'title' => 'Files', 73 + 'device' => true, 95 74 )); 96 75 } 97 76 ··· 141 120 return $list_view; 142 121 } 143 122 144 - private function buildSideNavView() { 145 - $view = new AphrontSideNavFilterView(); 146 - $view->setBaseURI(new PhutilURI($this->getApplicationURI('/filter/'))); 147 - 148 - $view->addLabel('Files'); 149 - $view->addFilter('upload', 'Upload File'); 150 - $view->addFilter('my', 'My Files'); 151 - $view->addFilter('all', 'All Files'); 152 - 153 - return $view; 154 - } 155 - 156 - private function renderUploadPanel() { 157 - $request = $this->getRequest(); 158 - $user = $request->getUser(); 159 - 160 - $limit_text = PhabricatorFileUploadView::renderUploadLimit(); 161 - 162 - if ($this->useBasicUploader()) { 163 - 164 - $upload_panel = new PhabricatorFileUploadView(); 165 - $upload_panel->setUser($user); 166 - 167 - } else { 168 - 169 - require_celerity_resource('files-css'); 170 - $upload_id = celerity_generate_unique_node_id(); 171 - $panel_id = celerity_generate_unique_node_id(); 172 - 173 - $upload_panel = new AphrontPanelView(); 174 - $upload_panel->setHeader('Upload Files'); 175 - $upload_panel->setCaption($limit_text); 176 - $upload_panel->setCreateButton('Basic Uploader', 177 - $request->getRequestURI()->setQueryParam('basic_uploader', true) 178 - ); 179 - 180 - $upload_panel->setWidth(AphrontPanelView::WIDTH_FULL); 181 - $upload_panel->setID($panel_id); 182 - 183 - $upload_panel->appendChild( 184 - phutil_render_tag( 185 - 'div', 186 - array( 187 - 'id' => $upload_id, 188 - 'style' => 'display: none;', 189 - 'class' => 'files-drag-and-drop', 190 - ), 191 - '')); 192 - 193 - Javelin::initBehavior( 194 - 'files-drag-and-drop', 195 - array( 196 - 'uri' => '/file/dropupload/', 197 - 'browseURI' => '/file/filter/my/', 198 - 'control' => $upload_id, 199 - 'target' => $panel_id, 200 - 'activatedClass' => 'aphront-panel-view-drag-and-drop', 201 - )); 202 - } 203 - 204 - return $upload_panel; 205 - } 206 123 }
+98 -12
src/applications/files/controller/PhabricatorFileUploadController.php
··· 3 3 final class PhabricatorFileUploadController extends PhabricatorFileController { 4 4 5 5 public function processRequest() { 6 - 7 6 $request = $this->getRequest(); 8 7 $user = $request->getUser(); 9 8 9 + $e_file = true; 10 + $errors = array(); 10 11 if ($request->isFormPost()) { 11 - $file = PhabricatorFile::newFromPHPUpload( 12 - idx($_FILES, 'file'), 13 - array( 14 - 'name' => $request->getStr('name'), 15 - 'authorPHID' => $user->getPHID(), 16 - )); 12 + if (!$request->getFileExists('file')) { 13 + $e_file = pht('Required'); 14 + $errors[] = pht('You must select a file to upload.'); 15 + } else { 16 + $file = PhabricatorFile::newFromPHPUpload( 17 + idx($_FILES, 'file'), 18 + array( 19 + 'name' => $request->getStr('name'), 20 + 'authorPHID' => $user->getPHID(), 21 + )); 22 + } 17 23 18 - return id(new AphrontRedirectResponse())->setURI($file->getBestURI()); 24 + if (!$errors) { 25 + return id(new AphrontRedirectResponse())->setURI($file->getViewURI()); 26 + } 19 27 } 20 28 21 - $panel = new PhabricatorFileUploadView(); 22 - $panel->setUser($user); 29 + $support_id = celerity_generate_unique_node_id(); 30 + $instructions = id(new AphrontFormMarkupControl()) 31 + ->setControlID($support_id) 32 + ->setControlStyle('display: none') 33 + ->setValue( 34 + '<br /><br />'. 35 + pht( 36 + '<strong>Drag and Drop:</strong> You can also upload files by '. 37 + 'dragging and dropping them from your desktop onto this page or '. 38 + 'the Phabricator home page.'). 39 + '<br /><br />'); 23 40 24 - return $this->buildStandardPageResponse( 25 - array($panel), 41 + $form = id(new AphrontFormView()) 42 + ->setFlexible(true) 43 + ->setUser($user) 44 + ->setEncType('multipart/form-data') 45 + ->appendChild( 46 + id(new AphrontFormFileControl()) 47 + ->setLabel(pht('File')) 48 + ->setName('file') 49 + ->setError($e_file) 50 + ->setCaption($this->renderUploadLimit())) 51 + ->appendChild( 52 + id(new AphrontFormTextControl()) 53 + ->setLabel(pht('Name')) 54 + ->setName('name') 55 + ->setValue($request->getStr('name')) 56 + ->setCaption('Optional file display name.')) 57 + ->appendChild( 58 + id(new AphrontFormSubmitControl()) 59 + ->setValue(pht('Upload')) 60 + ->addCancelButton('/file/')) 61 + ->appendChild($instructions); 62 + 63 + $crumbs = $this->buildApplicationCrumbs(); 64 + $crumbs->addCrumb( 65 + id(new PhabricatorCrumbView()) 66 + ->setName(pht('Upload')) 67 + ->setHref($request->getRequestURI())); 68 + 69 + $header = id(new PhabricatorHeaderView()) 70 + ->setHeader(pht('Upload File')); 71 + 72 + if ($errors) { 73 + $errors = id(new AphrontErrorView()) 74 + ->setTitle('Form Errors') 75 + ->setErrors($errors); 76 + } 77 + 78 + $global_upload = id(new PhabricatorGlobalUploadTargetView()) 79 + ->setShowIfSupportedID($support_id); 80 + 81 + return $this->buildApplicationPage( 82 + array( 83 + $crumbs, 84 + $header, 85 + $errors, 86 + $form, 87 + $global_upload, 88 + ), 26 89 array( 27 90 'title' => 'Upload File', 91 + 'device' => true, 28 92 )); 29 93 } 94 + 95 + private function renderUploadLimit() { 96 + $limit = PhabricatorEnv::getEnvConfig('storage.upload-size-limit'); 97 + $limit = phabricator_parse_bytes($limit); 98 + if ($limit) { 99 + $formatted = phabricator_format_bytes($limit); 100 + return 'Maximum file size: '.phutil_escape_html($formatted); 101 + } 102 + 103 + $doc_href = PhabricatorEnv::getDocLink( 104 + 'article/Configuring_File_Upload_Limits.html'); 105 + $doc_link = phutil_render_tag( 106 + 'a', 107 + array( 108 + 'href' => $doc_href, 109 + 'target' => '_blank', 110 + ), 111 + 'Configuring File Upload Limits'); 112 + 113 + return 'Upload limit is not configured, see '.$doc_link.'.'; 114 + } 115 + 30 116 }
-73
src/applications/files/view/PhabricatorFileUploadView.php
··· 1 - <?php 2 - 3 - final class PhabricatorFileUploadView extends AphrontView { 4 - 5 - private $user; 6 - 7 - private function getUser() { 8 - return $this->user; 9 - } 10 - public function setUser(PhabricatorUser $user) { 11 - $this->user = $user; 12 - return $this; 13 - } 14 - 15 - public function render() { 16 - $user = $this->getUser(); 17 - if (!$user) { 18 - throw new Exception("Call setUser() before render()!"); 19 - } 20 - 21 - $form = new AphrontFormView(); 22 - $form->setAction('/file/upload/'); 23 - $form->setUser($user); 24 - 25 - $form 26 - ->setEncType('multipart/form-data') 27 - ->appendChild( 28 - id(new AphrontFormFileControl()) 29 - ->setLabel('File') 30 - ->setName('file') 31 - ->setError(true) 32 - ->setCaption(self::renderUploadLimit())) 33 - ->appendChild( 34 - id(new AphrontFormTextControl()) 35 - ->setLabel('Name') 36 - ->setName('name') 37 - ->setCaption('Optional file display name.')) 38 - ->appendChild( 39 - id(new AphrontFormSubmitControl()) 40 - ->setValue('Upload') 41 - ->addCancelButton('/file/')); 42 - 43 - $panel = new AphrontPanelView(); 44 - $panel->setHeader('Upload File'); 45 - 46 - $panel->appendChild($form); 47 - $panel->setWidth(AphrontPanelView::WIDTH_FULL); 48 - 49 - return $panel->render(); 50 - } 51 - 52 - public static function renderUploadLimit() { 53 - $limit = PhabricatorEnv::getEnvConfig('storage.upload-size-limit'); 54 - $limit = phabricator_parse_bytes($limit); 55 - if ($limit) { 56 - $formatted = phabricator_format_bytes($limit); 57 - return 'Maximum file size: '.phutil_escape_html($formatted); 58 - } 59 - 60 - $doc_href = PhabricatorEnv::getDocLink( 61 - 'article/Configuring_File_Upload_Limits.html'); 62 - $doc_link = phutil_render_tag( 63 - 'a', 64 - array( 65 - 'href' => $doc_href, 66 - 'target' => '_blank', 67 - ), 68 - 'Configuring File Upload Limits'); 69 - 70 - return 'Upload limit is not configured, see '.$doc_link.'.'; 71 - } 72 - } 73 -
+37
src/applications/files/view/PhabricatorGlobalUploadTargetView.php
··· 1 + <?php 2 + 3 + final class PhabricatorGlobalUploadTargetView extends AphrontView { 4 + 5 + private $showIfSupportedID; 6 + 7 + public function setShowIfSupportedID($show_if_supported_id) { 8 + $this->showIfSupportedID = $show_if_supported_id; 9 + return $this; 10 + } 11 + 12 + public function getShowIfSupportedID() { 13 + return $this->showIfSupportedID; 14 + } 15 + 16 + public function render() { 17 + $instructions_id = celerity_generate_unique_node_id(); 18 + 19 + require_celerity_resource('global-drag-and-drop-css'); 20 + 21 + Javelin::initBehavior('global-drag-and-drop', array( 22 + 'ifSupported' => $this->showIfSupportedID, 23 + 'instructions' => $instructions_id, 24 + 'uploadURI' => '/file/dropupload/', 25 + 'browseURI' => '/file/filter/my/', 26 + )); 27 + 28 + return phutil_render_tag( 29 + 'div', 30 + array( 31 + 'id' => $instructions_id, 32 + 'class' => 'phabricator-global-upload-instructions', 33 + 'style' => 'display: none;', 34 + ), 35 + pht("\xE2\x87\xAA Drop Files to Upload")); 36 + } 37 + }
-1
src/view/form/control/AphrontFormTextAreaControl.php
··· 11 11 12 12 private $height; 13 13 private $readOnly; 14 - private $enableDragAndDropFileUploads; 15 14 private $customClass; 16 15 17 16 public function setHeight($height) {
+8 -1
src/view/layout/AphrontSideNavFilterView.php
··· 32 32 $this->menu = new PhabricatorMenuView(); 33 33 } 34 34 35 + public static function newFromMenu(PhabricatorMenuView $menu) { 36 + $object = new AphrontSideNavFilterView(); 37 + $object->setBaseURI(new PhutilURI('/')); 38 + $object->menu = $menu; 39 + return $object; 40 + } 41 + 35 42 public function setCrumbs(PhabricatorCrumbsView $crumbs) { 36 43 $this->crumbs = $crumbs; 37 44 return $this; ··· 128 135 129 136 public function selectFilter($key, $default = null) { 130 137 $this->selectedFilter = $default; 131 - if ($this->menu->getItem($key)) { 138 + if ($this->menu->getItem($key) && strlen($key)) { 132 139 $this->selectedFilter = $key; 133 140 } 134 141 return $this->selectedFilter;
+25 -10
src/view/layout/PhabricatorMenuView.php
··· 3 3 final class PhabricatorMenuView extends AphrontView { 4 4 5 5 private $items = array(); 6 - private $map = array(); 7 6 private $classes = array(); 8 7 9 8 public function addClass($class) { ··· 34 33 35 34 public function addMenuItem(PhabricatorMenuItemView $item) { 36 35 $key = $item->getKey(); 37 - if ($key !== null) { 38 - if (isset($this->map[$key])) { 39 - throw new Exception( 40 - "Menu contains duplicate items with key '{$key}'!"); 41 - } 42 - $this->map[$key] = $item; 43 - } 44 - 45 36 $this->items[] = $item; 46 37 $this->appendChild($item); 47 38 ··· 49 40 } 50 41 51 42 public function getItem($key) { 52 - return idx($this->map, (string)$key); 43 + $key = (string)$key; 44 + 45 + // NOTE: We could optimize this, but need to update any map when items have 46 + // their keys change. Since that's moderately complex, wait for a profile 47 + // or use case. 48 + 49 + foreach ($this->items as $item) { 50 + if ($item->getKey() == $key) { 51 + return $item; 52 + } 53 + } 54 + 55 + return null; 53 56 } 54 57 55 58 public function getItems() { ··· 57 60 } 58 61 59 62 public function render() { 63 + $key_map = array(); 64 + foreach ($this->items as $item) { 65 + $key = $item->getKey(); 66 + if ($key !== null) { 67 + if (isset($key_map[$key])) { 68 + throw new Exception( 69 + "Menu contains duplicate items with key '{$key}'!"); 70 + } 71 + $key_map[$key] = $item; 72 + } 73 + } 74 + 60 75 $classes = $this->classes; 61 76 $classes[] = 'phabricator-menu-view'; 62 77
-10
webroot/rsrc/css/application/files/files.css
··· 1 - /** 2 - * @provides files-css 3 - */ 4 - 5 - .files-drag-and-drop { 6 - text-align: center; 7 - padding: 0em 1em .5em; 8 - font-size: 15px; 9 - color: #666666; 10 - }
+22
webroot/rsrc/css/application/files/global-drag-and-drop.css
··· 1 + /** 2 + * @provides global-drag-and-drop-css 3 + */ 4 + 5 + .phabricator-global-upload-instructions { 6 + text-align: center; 7 + position: fixed; 8 + font-size: 36px; 9 + font-weight: bold; 10 + 11 + margin: 0 10%; 12 + width: 80%; 13 + left: 0; 14 + top: 30%; 15 + padding: 18px 0; 16 + 17 + color: #ffffff; 18 + background: rgba(0, 0, 0, 0.8); 19 + z-index: 8; 20 + border-radius: 18px; 21 + 22 + }
+18 -5
webroot/rsrc/js/application/core/DragAndDropFileUpload.js
··· 15 15 this._node = node; 16 16 }, 17 17 18 - events : ['willUpload', 'progress', 'didUpload', 'didError'], 18 + events : [ 19 + 'didBeginDrag', 20 + 'didEndDrag', 21 + 'willUpload', 22 + 'progress', 23 + 'didUpload', 24 + 'didError'], 19 25 20 26 statics : { 21 27 isSupported : function() { 22 28 // TODO: Is there a better capability test for this? This seems okay in 23 29 // Safari, Firefox and Chrome. 30 + 24 31 return !!window.FileList; 25 32 } 26 33 }, ··· 29 36 _node : null, 30 37 _depth : 0, 31 38 _updateDepth : function(delta) { 39 + if (this._depth == 0 && delta > 0) { 40 + JX.log('begin: ' + this._depth + ' @ ' + delta); 41 + this.invoke('didBeginDrag'); 42 + } 43 + 32 44 this._depth += delta; 33 - JX.DOM.alterClass( 34 - this._node, 35 - this.getActivatedClass(), 36 - (this._depth > 0)); 45 + 46 + if (this._depth == 0 && delta < 0) { 47 + JX.log('end: ' + this._depth + ' @ ' + delta); 48 + this.invoke('didEndDrag'); 49 + } 37 50 }, 38 51 39 52 start : function() {
+18 -2
webroot/rsrc/js/application/core/behavior-drag-and-drop-textarea.js
··· 12 12 var target = JX.$(config.target); 13 13 14 14 function onupload(f) { 15 - JX.TextAreaUtils.setSelectionText(target, '{F' + f.getID() + '}'); 15 + var text = JX.TextAreaUtils.getSelectionText(target); 16 + var ref = '{F' + f.getID() + '}'; 17 + 18 + // If the user has dragged and dropped multiple files, we'll get an event 19 + // each time an upload completes. Rather than overwriting the first 20 + // reference, append the new reference if the selected text looks like an 21 + // existing file reference. 22 + if (text.match(/^\{F/)) { 23 + ref = text + "\n\n" + ref; 24 + } 25 + 26 + JX.TextAreaUtils.setSelectionText(target, ref); 16 27 } 17 28 18 29 if (JX.PhabricatorDragAndDropFileUpload.isSupported()) { 19 30 var drop = new JX.PhabricatorDragAndDropFileUpload(target) 20 - .setActivatedClass(config.activatedClass) 21 31 .setURI(config.uri); 32 + drop.listen('didBeginDrag', function(e) { 33 + JX.DOM.alterClass(target, config.activatedClass, true); 34 + }); 35 + drop.listen('didEndDrag', function(e) { 36 + JX.DOM.alterClass(target, config.activatedClass, false); 37 + }); 22 38 drop.listen('didUpload', onupload); 23 39 drop.start(); 24 40 }
+8 -2
webroot/rsrc/js/application/core/behavior-drag-and-drop.js
··· 2 2 * @provides javelin-behavior-aphront-drag-and-drop 3 3 * @requires javelin-behavior 4 4 * javelin-dom 5 - * javelin-util 6 5 * phabricator-drag-and-drop-file-upload 7 6 */ 8 7 ··· 23 22 var list = JX.$(config.list); 24 23 25 24 var drop = new JX.PhabricatorDragAndDropFileUpload(JX.$(config.list)) 26 - .setActivatedClass(config.activatedClass) 27 25 .setURI(config.uri); 26 + 27 + drop.listen('didBeginDrag', function(e) { 28 + JX.DOM.alterClass(list, config.activatedClass, true); 29 + }); 30 + 31 + drop.listen('didEndDrag', function(e) { 32 + JX.DOM.alterClass(list, config.activatedClass, false); 33 + }); 28 34 29 35 drop.listen('willUpload', function(f) { 30 36 pending++;
-84
webroot/rsrc/js/application/core/behavior-files-drag-and-drop.js
··· 1 - /** 2 - * @provides javelin-behavior-files-drag-and-drop 3 - * @requires javelin-behavior 4 - * javelin-dom 5 - * javelin-uri 6 - * phabricator-drag-and-drop-file-upload 7 - */ 8 - 9 - JX.behavior('files-drag-and-drop', function(config) { 10 - 11 - // The control renders hidden by default; if we don't have support for 12 - // drag-and-drop just leave it hidden. 13 - if (!JX.PhabricatorDragAndDropFileUpload.isSupported()) { 14 - return; 15 - } 16 - 17 - var pending = 0; 18 - var files = []; 19 - var errors = false; 20 - 21 - var control = JX.$(config.control); 22 - // Show the control, since we have browser support. 23 - control.style.display = ''; 24 - 25 - var drop = new JX.PhabricatorDragAndDropFileUpload(JX.$(config.target)) 26 - .setActivatedClass(config.activatedClass) 27 - .setURI(config.uri); 28 - 29 - drop.listen('willUpload', function(f) { 30 - pending++; 31 - redraw(); 32 - }); 33 - 34 - drop.listen('didUpload', function(f) { 35 - files.push(f); 36 - 37 - pending--; 38 - if (pending == 0 && !errors) { 39 - // If whatever the user dropped in has finished uploading, send them to 40 - // their uploads. 41 - var uri; 42 - uri = JX.$U(config.browseURI); 43 - var ids = []; 44 - for (var ii = 0; ii < files.length; ii++) { 45 - ids.push(files[ii].getID()); 46 - } 47 - uri.setQueryParam('h', ids.join(',')); 48 - 49 - // Reset so if you hit 'back' into the bfcache the page is still in a 50 - // sensible state. 51 - redraw(); 52 - files = []; 53 - 54 - uri.go(); 55 - } else { 56 - redraw(); 57 - } 58 - }); 59 - 60 - drop.listen('didError', function(f) { 61 - pending--; 62 - errors = true; 63 - redraw(); 64 - }); 65 - 66 - drop.start(); 67 - redraw(); 68 - 69 - function redraw() { 70 - 71 - var status; 72 - if (pending) { 73 - status = 'Uploading ' + pending + ' files...'; 74 - } else { 75 - var arrow = String.fromCharCode(0x21EA); 76 - status = JX.$H( 77 - arrow + ' <strong>Drag and Drop</strong> files here to upload them.'); 78 - } 79 - 80 - JX.DOM.setContent(control, status); 81 - } 82 - 83 - }); 84 -
+68
webroot/rsrc/js/application/core/behavior-global-drag-and-drop.js
··· 1 + /** 2 + * @provides javelin-behavior-global-drag-and-drop 3 + * @requires javelin-behavior 4 + * javelin-dom 5 + * javelin-uri 6 + * javelin-mask 7 + * phabricator-drag-and-drop-file-upload 8 + */ 9 + 10 + JX.behavior('global-drag-and-drop', function(config) { 11 + if (!JX.PhabricatorDragAndDropFileUpload.isSupported()) { 12 + return; 13 + } 14 + 15 + var pending = 0; 16 + var files = []; 17 + var errors = false; 18 + 19 + if (config.ifSupported) { 20 + JX.$(config.ifSupported).style.display = ''; 21 + } 22 + 23 + var drop = new JX.PhabricatorDragAndDropFileUpload(document.documentElement) 24 + .setURI(config.uploadURI); 25 + 26 + drop.listen('didBeginDrag', function(f) { 27 + JX.Mask.show(); 28 + JX.DOM.show(JX.$(config.instructions)); 29 + }); 30 + 31 + drop.listen('didEndDrag', function(f) { 32 + JX.Mask.hide(); 33 + JX.DOM.hide(JX.$(config.instructions)); 34 + }); 35 + 36 + drop.listen('willUpload', function(f) { 37 + pending++; 38 + }); 39 + 40 + drop.listen('didUpload', function(f) { 41 + files.push(f); 42 + 43 + pending--; 44 + if (pending == 0 && !errors) { 45 + // If whatever the user dropped in has finished uploading, send them to 46 + // their uploads. 47 + var uri; 48 + uri = JX.$U(config.browseURI); 49 + var ids = []; 50 + for (var ii = 0; ii < files.length; ii++) { 51 + ids.push(files[ii].getID()); 52 + } 53 + uri.setQueryParam('h', ids.join(',')); 54 + 55 + files = []; 56 + 57 + uri.go(); 58 + } 59 + }); 60 + 61 + drop.listen('didError', function(f) { 62 + pending--; 63 + errors = true; 64 + }); 65 + 66 + drop.start(); 67 + }); 68 +