Index: src/__celerity_resource_map__.php =================================================================== --- src/__celerity_resource_map__.php +++ src/__celerity_resource_map__.php @@ -2267,7 +2267,7 @@ ), 'javelin-behavior-policy-control' => array( - 'uri' => '/res/f43ba427/rsrc/js/application/policy/behavior-policy-control.js', + 'uri' => '/res/ce9f54c8/rsrc/js/application/policy/behavior-policy-control.js', 'type' => 'js', 'requires' => array( @@ -2276,6 +2276,7 @@ 2 => 'javelin-util', 3 => 'phabricator-dropdown-menu', 4 => 'phabricator-menu-item', + 5 => 'javelin-workflow', ), 'disk' => '/rsrc/js/application/policy/behavior-policy-control.js', ), Index: src/view/form/control/AphrontFormPolicyControl.php =================================================================== --- src/view/form/control/AphrontFormPolicyControl.php +++ src/view/form/control/AphrontFormPolicyControl.php @@ -54,6 +54,48 @@ 'icon' => $policy->getIcon(), ); } + + // If we were passed several custom policy options, throw away the ones + // which aren't the value for this capability. For example, an object might + // have a custom view pollicy and a custom edit policy. When we render + // the selector for "Can View", we don't want to show the "Can Edit" + // custom policy -- if we did, the menu would look like this: + // + // Custom + // Custom Policy + // Custom Policy + // + // ...where one is the "view" custom policy, and one is the "edit" custom + // policy. + + $type_custom = PhabricatorPolicyType::TYPE_CUSTOM; + if (!empty($options[$type_custom])) { + $options[$type_custom] = array_select_keys( + $options[$type_custom], + array($this->getValue())); + } + + // If there aren't any custom policies, add a placeholder policy so we + // render a menu item. This allows the user to switch to a custom policy. + + if (empty($options[$type_custom])) { + $placeholder = new PhabricatorPolicy(); + $placeholder->setName(pht('Custom Policy...')); + $options[$type_custom][$this->getCustomPolicyPlaceholder()] = array( + 'name' => $placeholder->getName(), + 'full' => $placeholder->getName(), + 'icon' => $placeholder->getIcon(), + ); + } + + $options = array_select_keys( + $options, + array( + PhabricatorPolicyType::TYPE_GLOBAL, + PhabricatorPolicyType::TYPE_CUSTOM, + PhabricatorPolicyType::TYPE_PROJECT, + )); + return $options; } @@ -121,6 +163,7 @@ 'icons' => $icons, 'labels' => $labels, 'value' => $this->getValue(), + 'customPlaceholder' => $this->getCustomPolicyPlaceholder(), )); $selected = $flat_options[$this->getValue()]; @@ -165,5 +208,8 @@ )); } + private function getCustomPolicyPlaceholder() { + return 'custom:placeholder'; + } } Index: webroot/rsrc/js/application/policy/behavior-policy-control.js =================================================================== --- webroot/rsrc/js/application/policy/behavior-policy-control.js +++ webroot/rsrc/js/application/policy/behavior-policy-control.js @@ -5,6 +5,7 @@ * javelin-util * phabricator-dropdown-menu * phabricator-menu-item + * javelin-workflow * @javelin */ JX.behavior('policy-control', function(config) { @@ -23,25 +24,37 @@ for (var ii = 0; ii < config.groups.length; ii++) { var group = config.groups[ii]; - var header = new JX.PhabricatorMenuItem(config.labels[group]); + var header = new JX.PhabricatorMenuItem(config.labels[group], JX.bag); header.setDisabled(true); menu.addItem(header); for (var jj = 0; jj < config.order[group].length; jj++) { var phid = config.order[group][jj]; - var option = config.options[phid]; - var render = [JX.$H(config.icons[option.icon]), option.name]; + var onselect; + if (group == 'custom') { + onselect = JX.bind(null, function(phid) { + var uri = get_custom_uri(phid); + + new JX.Workflow(uri) + .setHandler(function(response) { + if (!response.phid) { + return; + } + + replace_policy(phid, response.phid, response.info); + select_policy(response.phid); + }) + .start(); + + }, phid); + } else { + onselect = JX.bind(null, select_policy, phid); + } var item = new JX.PhabricatorMenuItem( - render, - JX.bind(null, function(phid, render) { - JX.DOM.setContent( - JX.DOM.find(control, 'span', 'policy-label'), - render); - input.value = phid; - value = phid; - }, phid, render)); + render_option(phid, true), + onselect); if (phid == value) { item.setSelected(true); @@ -53,4 +66,56 @@ }); + + var select_policy = function(phid) { + JX.DOM.setContent( + JX.DOM.find(control, 'span', 'policy-label'), + render_option(phid)); + + input.value = phid; + value = phid; + }; + + + var render_option = function(phid, with_title) { + var option = config.options[phid]; + + var name = option.name; + if (with_title && (option.full != option.name)) { + name = JX.$N('span', {title: option.full}, name); + } + + return [JX.$H(config.icons[option.icon]), name]; + }; + + + /** + * Get the workflow URI to create or edit a policy with a given PHID. + */ + var get_custom_uri = function(phid) { + var uri = '/policy/edit/'; + if (phid != config.customPlaceholder) { + uri += phid + '/'; + } + return uri; + }; + + + /** + * Replace an existing policy option with a new one. Used to swap out custom + * policies after the user edits them. + */ + var replace_policy = function(old_phid, new_phid, info) { + config.options[new_phid] = info; + for (var k in config.order) { + for (var ii = 0; ii < config.order[k].length; ii++) { + if (config.order[k][ii] == old_phid) { + config.order[k][ii] = new_phid; + return; + } + } + } + }; + + });