From 7abdf3afe0a8458be1833c5683f6aecb5f5f9cb2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 4 Jan 2012 16:52:13 -0800 Subject: [PATCH] Add a dropdown menu for Differential view options Summary: There are several open Differential tasks that are basically blocked on not having reasonable places in the UI to put things. Replace the "View Standalone / Raw" button with a "View Options" dropdown menu so we can shove things like "Expand All", "Fold / Unfold File", and "View in Diffusion" in there. This doesn't change any behavior, just puts the existing options in a menu. Test Plan: - Toggled menu open by clicking button. - Clicked menu items. - Toggled menu closed by clicking button. - Toggled menu closed by clicking document. - Toggled menu closed by opening another menu. - Toggled menu closed by selecting an item. Reviewers: btrahan, jungejason Reviewed By: btrahan CC: aran, btrahan Maniphest Tasks: T497, T309 Differential Revision: https://secure.phabricator.com/D1316 --- src/__celerity_resource_map__.php | 120 ++++++++++----- .../DifferentialChangesetViewController.php | 19 --- .../controller/changesetview/__init__.php | 1 - .../DifferentialChangesetListView.php | 18 ++- webroot/rsrc/css/core/buttons.css | 37 ++++- .../rsrc/js/application/core/DropdownMenu.js | 140 ++++++++++++++++++ .../js/application/core/DropdownMenuItem.js | 28 ++++ .../differential/behavior-dropdown-menus.js | 48 ++++++ 8 files changed, 347 insertions(+), 64 deletions(-) create mode 100644 webroot/rsrc/js/application/core/DropdownMenu.js create mode 100644 webroot/rsrc/js/application/core/DropdownMenuItem.js create mode 100644 webroot/rsrc/js/application/differential/behavior-dropdown-menus.js diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index e699c9e672..4c1e11945a 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -470,6 +470,20 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/application/differential/behavior-diff-radios.js', ), + 'javelin-behavior-differential-dropdown-menus' => + array( + 'uri' => '/res/08e751ee/rsrc/js/application/differential/behavior-dropdown-menus.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-dom', + 2 => 'javelin-stratcom', + 3 => 'phabricator-dropdown-menu', + 4 => 'phabricator-menu-item', + ), + 'disk' => '/rsrc/js/application/differential/behavior-dropdown-menus.js', + ), 'javelin-behavior-differential-edit-inline-comments' => array( 'uri' => '/res/c24338ce/rsrc/js/application/differential/behavior-edit-inline-comments.js', @@ -1303,7 +1317,7 @@ celerity_register_resource_map(array( ), 'phabricator-core-buttons-css' => array( - 'uri' => '/res/3059cf79/rsrc/css/core/buttons.css', + 'uri' => '/res/89b939ae/rsrc/css/core/buttons.css', 'type' => 'css', 'requires' => array( @@ -1351,6 +1365,21 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/application/core/DragAndDropFileUpload.js', ), + 'phabricator-dropdown-menu' => + array( + 'uri' => '/res/d55c3771/rsrc/js/application/core/DropdownMenu.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + 2 => 'javelin-dom', + 3 => 'javelin-vector', + 4 => 'javelin-stratcom', + 5 => 'phabricator-menu-item', + ), + 'disk' => '/rsrc/js/application/core/DropdownMenu.js', + ), 'phabricator-feed-css' => array( 'uri' => '/res/7d1d0015/rsrc/css/application/feed/feed.css', @@ -1386,6 +1415,17 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/application/core/KeyboardShortcutManager.js', ), + 'phabricator-menu-item' => + array( + 'uri' => '/res/8a9be282/rsrc/js/application/core/DropdownMenuItem.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + 1 => 'javelin-dom', + ), + 'disk' => '/rsrc/js/application/core/DropdownMenuItem.js', + ), 'phabricator-object-selector-css' => array( 'uri' => '/res/608461d2/rsrc/css/application/objectselector/object-selector.css', @@ -1707,30 +1747,6 @@ celerity_register_resource_map(array( 'uri' => '/res/pkg/4e7acf1a/core.pkg.js', 'type' => 'js', ), - '4f6e449d' => - array( - 'name' => 'core.pkg.css', - 'symbols' => - array( - 0 => 'phabricator-core-css', - 1 => 'phabricator-core-buttons-css', - 2 => 'phabricator-standard-page-view', - 3 => 'aphront-dialog-view-css', - 4 => 'aphront-form-view-css', - 5 => 'aphront-panel-view-css', - 6 => 'aphront-side-nav-view-css', - 7 => 'aphront-table-view-css', - 8 => 'aphront-crumbs-view-css', - 9 => 'aphront-tokenizer-control-css', - 10 => 'aphront-typeahead-control-css', - 11 => 'aphront-list-filter-view-css', - 12 => 'phabricator-directory-css', - 13 => 'phabricator-remarkup-css', - 14 => 'syntax-highlighting-css', - ), - 'uri' => '/res/pkg/4f6e449d/core.pkg.css', - 'type' => 'css', - ), '540effd7' => array( 'name' => 'typeahead.pkg.js', @@ -1787,19 +1803,43 @@ celerity_register_resource_map(array( 'uri' => '/res/pkg/b164acea/javelin.pkg.js', 'type' => 'js', ), + 'faf854fc' => + array( + 'name' => 'core.pkg.css', + 'symbols' => + array( + 0 => 'phabricator-core-css', + 1 => 'phabricator-core-buttons-css', + 2 => 'phabricator-standard-page-view', + 3 => 'aphront-dialog-view-css', + 4 => 'aphront-form-view-css', + 5 => 'aphront-panel-view-css', + 6 => 'aphront-side-nav-view-css', + 7 => 'aphront-table-view-css', + 8 => 'aphront-crumbs-view-css', + 9 => 'aphront-tokenizer-control-css', + 10 => 'aphront-typeahead-control-css', + 11 => 'aphront-list-filter-view-css', + 12 => 'phabricator-directory-css', + 13 => 'phabricator-remarkup-css', + 14 => 'syntax-highlighting-css', + ), + 'uri' => '/res/pkg/faf854fc/core.pkg.css', + 'type' => 'css', + ), ), 'reverse' => array( - 'aphront-crumbs-view-css' => '4f6e449d', - 'aphront-dialog-view-css' => '4f6e449d', - 'aphront-form-view-css' => '4f6e449d', + 'aphront-crumbs-view-css' => 'faf854fc', + 'aphront-dialog-view-css' => 'faf854fc', + 'aphront-form-view-css' => 'faf854fc', 'aphront-headsup-action-list-view-css' => '8b139246', - 'aphront-list-filter-view-css' => '4f6e449d', - 'aphront-panel-view-css' => '4f6e449d', - 'aphront-side-nav-view-css' => '4f6e449d', - 'aphront-table-view-css' => '4f6e449d', - 'aphront-tokenizer-control-css' => '4f6e449d', - 'aphront-typeahead-control-css' => '4f6e449d', + 'aphront-list-filter-view-css' => 'faf854fc', + 'aphront-panel-view-css' => 'faf854fc', + 'aphront-side-nav-view-css' => 'faf854fc', + 'aphront-table-view-css' => 'faf854fc', + 'aphront-tokenizer-control-css' => 'faf854fc', + 'aphront-typeahead-control-css' => 'faf854fc', 'differential-changeset-view-css' => '8b139246', 'differential-core-view-css' => '8b139246', 'differential-inline-comment-editor' => '11a5c52c', @@ -1848,16 +1888,16 @@ celerity_register_resource_map(array( 'javelin-vector' => 'b164acea', 'javelin-workflow' => '4e7acf1a', 'phabricator-content-source-view-css' => '8b139246', - 'phabricator-core-buttons-css' => '4f6e449d', - 'phabricator-core-css' => '4f6e449d', - 'phabricator-directory-css' => '4f6e449d', + 'phabricator-core-buttons-css' => 'faf854fc', + 'phabricator-core-css' => 'faf854fc', + 'phabricator-directory-css' => 'faf854fc', 'phabricator-drag-and-drop-file-upload' => '11a5c52c', 'phabricator-keyboard-shortcut' => '4e7acf1a', 'phabricator-keyboard-shortcut-manager' => '4e7acf1a', 'phabricator-object-selector-css' => '8b139246', - 'phabricator-remarkup-css' => '4f6e449d', + 'phabricator-remarkup-css' => 'faf854fc', 'phabricator-shaped-request' => '11a5c52c', - 'phabricator-standard-page-view' => '4f6e449d', - 'syntax-highlighting-css' => '4f6e449d', + 'phabricator-standard-page-view' => 'faf854fc', + 'syntax-highlighting-css' => 'faf854fc', ), )); diff --git a/src/applications/differential/controller/changesetview/DifferentialChangesetViewController.php b/src/applications/differential/controller/changesetview/DifferentialChangesetViewController.php index 591a7faf70..24ab8d280e 100644 --- a/src/applications/differential/controller/changesetview/DifferentialChangesetViewController.php +++ b/src/applications/differential/controller/changesetview/DifferentialChangesetViewController.php @@ -194,25 +194,6 @@ class DifferentialChangesetViewController extends DifferentialController { $detail->setChangeset($changeset); $detail->appendChild($output); - if (!$vs) { - $detail->addButton( - phutil_render_tag( - 'a', - array( - 'href' => $request->getRequestURI()->alter('view', 'old'), - 'class' => 'grey button small', - ), - 'View Raw File (Old Version)')); - $detail->addButton( - phutil_render_tag( - 'a', - array( - 'href' => $request->getRequestURI()->alter('view', 'new'), - 'class' => 'grey button small', - ), - 'View Raw File (New Version)')); - } - $detail->setRevisionID($request->getInt('revision_id')); $output = diff --git a/src/applications/differential/controller/changesetview/__init__.php b/src/applications/differential/controller/changesetview/__init__.php index 4def808d80..500b73b624 100644 --- a/src/applications/differential/controller/changesetview/__init__.php +++ b/src/applications/differential/controller/changesetview/__init__.php @@ -22,7 +22,6 @@ phutil_require_module('phabricator', 'applications/phid/handle/data'); phutil_require_module('phabricator', 'infrastructure/diff/engine'); phutil_require_module('phabricator', 'infrastructure/javelin/api'); -phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/differential/view/changesetlistview/DifferentialChangesetListView.php b/src/applications/differential/view/changesetlistview/DifferentialChangesetListView.php index a847219e0b..7498703ee2 100644 --- a/src/applications/differential/view/changesetlistview/DifferentialChangesetListView.php +++ b/src/applications/differential/view/changesetlistview/DifferentialChangesetListView.php @@ -1,7 +1,7 @@ changesets; + if ($this->standaloneViews) { + Javelin::initBehavior( + 'differential-dropdown-menus', + array()); + } + $output = array(); $mapping = array(); foreach ($changesets as $key => $changeset) { @@ -91,14 +97,20 @@ class DifferentialChangesetListView extends AphrontView { 'whitespace' => $this->whitespace, )); - $detail_button = phutil_render_tag( + $detail_button = javelin_render_tag( 'a', array( 'class' => 'button small grey', + 'meta' => array( + 'detailURI' => (string)$detail_uri, + 'leftURI' => (string)$detail_uri->alter('view', 'old'), + 'rightURI' => (string)$detail_uri->alter('view', 'new'), + ), 'href' => $detail_uri, 'target' => '_blank', + 'sigil' => 'differential-view-options', ), - 'View Standalone / Raw'); + "View Options \xE2\x96\xBC"); } $uniq_id = celerity_generate_unique_node_id(); diff --git a/webroot/rsrc/css/core/buttons.css b/webroot/rsrc/css/core/buttons.css index 2288ebf5df..180537fa49 100644 --- a/webroot/rsrc/css/core/buttons.css +++ b/webroot/rsrc/css/core/buttons.css @@ -101,7 +101,8 @@ button.disabled { button.grey:active, a.grey:active, -button.grey_active { +button.grey_active, +a.dropdown-open { background-color: #dddddd; background-position: 0 -200px; border-bottom-color: #999; @@ -185,3 +186,37 @@ a.toggle-fixed { a.toggle-fixed:hover { background: #909090; } + + +a.dropdown-open { +} + +.dropdown-menu-frame { + z-index: 32; + position: absolute; + width: 240px; + background: #f6f6f6; + border: 1px solid #999; + margin-top: -1px; + + box-shadow: 1px 3px 1px rgba(0, 0, 0, 0.25); + -moz-box-shadow: 1px 3px 1px rgba(0, 0, 0, 0.25); + -webkit-box-shadow: 1px 3px 1px rgba(0, 0, 0, 0.25); +} + +.dropdown-menu-frame a, +.dropdown-menu-frame span { + display: block; + font-size: 11px; + padding: 4px 8px; +} + +.dropdown-menu-frame span { + color: #666666; +} + +.dropdown-menu-frame a:hover { + background: #005588; + color: white; + text-decoration: none; +} diff --git a/webroot/rsrc/js/application/core/DropdownMenu.js b/webroot/rsrc/js/application/core/DropdownMenu.js new file mode 100644 index 0000000000..a5b0c8fc0a --- /dev/null +++ b/webroot/rsrc/js/application/core/DropdownMenu.js @@ -0,0 +1,140 @@ +/** + * @requires javelin-install + * javelin-util + * javelin-dom + * javelin-vector + * javelin-stratcom + * phabricator-menu-item + * @provides phabricator-dropdown-menu + * @javelin + */ + +JX.install('PhabricatorDropdownMenu', { + + construct : function(node) { + this._node = node; + this._items = []; + this._menu = JX.$N('div', { className : 'dropdown-menu-frame' }); + + JX.DOM.listen( + this._node, + 'click', + null, + JX.bind(this, this._onclick)); + + JX.DOM.listen( + this._menu, + 'click', + null, + JX.bind(this, this._onclickitem)); + + JX.Stratcom.listen( + 'mousedown', + null, + JX.bind(this, this._onclickglobal)); + + JX.PhabricatorDropdownMenu.listen( + 'open', + JX.bind(this, this.close)); + }, + + events : ['open'], + + members : { + _node : null, + _menu : null, + _open : false, + _items : null, + + open : function() { + if (this._open) { + return; + } + + this.invoke('open'); + + var menu_items = []; + for (var ii = 0; ii < this._items.length; ii++) { + menu_items.push(this._items[ii].render()); + } + JX.DOM.setContent(this._menu, menu_items); + + this._open = true; + this._show(); + }, + + close : function() { + if (!this._open) { + return; + } + this._open = false; + this._hide(); + }, + + addItem : function(item) { + if (__DEV__) { + if (!(item instanceof JX.PhabricatorMenuItem)) { + JX.$E( + 'JX.DropdownMenu.addItem(): ' + + 'item must be a JX.PhabricatorMenuItem.'); + } + } + this._items.push(item); + return this; + }, + + _onclick : function(e) { + if (this._open) { + this.close(); + } else { + this.open(); + } + e.prevent(); + }, + + _onclickitem : function(e) { + var item = JX.Stratcom.getData(e.getTarget()).item; + if (!item) { + return; + } + item.select(); + e.prevent(); + this.close(); + }, + + _onclickglobal : function(e) { + if (JX.Stratcom.pass(e)) { + return; + } + + var t = e.getTarget(); + while (t) { + if (t == this._menu || t == this._node) { + return; + } + t = t.parentNode; + } + + this.close(); + }, + + _show : function() { + document.body.appendChild(this._menu); + + var m = JX.Vector.getDim(this._menu); + + JX.$V(this._node) + .add(JX.Vector.getDim(this._node)) + .add(JX.$V(-m.x, 0)) + .setPos(this._menu); + + JX.DOM.alterClass(this._node, 'dropdown-open', true); + }, + + _hide : function() { + JX.DOM.remove(this._menu); + JX.DOM.alterClass(this._node, 'dropdown-open', false); + } + + } +}); diff --git a/webroot/rsrc/js/application/core/DropdownMenuItem.js b/webroot/rsrc/js/application/core/DropdownMenuItem.js new file mode 100644 index 0000000000..b15eb435d9 --- /dev/null +++ b/webroot/rsrc/js/application/core/DropdownMenuItem.js @@ -0,0 +1,28 @@ +/** + * @requires javelin-install + * javelin-dom + * @provides phabricator-menu-item + * @javelin + */ + +JX.install('PhabricatorMenuItem', { + + construct : function(name, action) { + this._name = name; + this._action = action; + }, + + members : { + _name : null, + _action : null, + + render : function() { + return JX.$N('a', { href : '#', meta : { item : this } }, this._name); + }, + + select : function() { + this._action(); + } + } + +}); diff --git a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js b/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js new file mode 100644 index 0000000000..497b353713 --- /dev/null +++ b/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js @@ -0,0 +1,48 @@ +/** + * @provides javelin-behavior-differential-dropdown-menus + * @requires javelin-behavior + * javelin-dom + * javelin-stratcom + * phabricator-dropdown-menu + * phabricator-menu-item + */ + +JX.behavior('differential-dropdown-menus', function(config) { + + function standalone(button) { + return function() { + window.open(JX.Stratcom.getData(button).detailURI); + } + } + + function left_file(button) { + return function() { + window.open(JX.Stratcom.getData(button).leftURI); + } + } + + function right_file(button) { + return function() { + window.open(JX.Stratcom.getData(button).rightURI); + } + } + + var buttons = JX.DOM.scry(window.document, 'a', 'differential-view-options'); + for (var ii = 0; ii < buttons.length; ii++) { + var button = buttons[ii]; + new JX.PhabricatorDropdownMenu(buttons[ii]) + .addItem( + new JX.PhabricatorMenuItem( + 'View Standalone', + standalone(button))) + .addItem( + new JX.PhabricatorMenuItem( + 'Show Raw File (Left)', + left_file(button))) + .addItem( + new JX.PhabricatorMenuItem( + 'Show Raw File (Right)', + right_file(button))); + } + +});