@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.

Rebuild Dashboards on EditEngine: v1 Major Jank Edition

Summary:
Depends on D20383. Ref T13272. Fixes T12363. See PHI997. This gets the edit flows for tab panels functional again. They aren't //nice//, and a lot of the workflows are fairly janky: for example, most of them end up with you on the tab panel's page, which isn't useful if you started on a dashboard page.

However, these flows were extremely janky before anyway (see T12363) and I suspect this is a net improvement even though it's a bit of a mess. I anticipate cleaning this up bit-by-bit in future diffs.

Test Plan: {F6366372}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13272, T12363

Differential Revision: https://secure.phabricator.com/D20384

+519 -28
+3 -3
resources/celerity/map.php
··· 9 9 'names' => array( 10 10 'conpherence.pkg.css' => '3c8a0668', 11 11 'conpherence.pkg.js' => '020aebcf', 12 - 'core.pkg.css' => '2d4810eb', 12 + 'core.pkg.css' => '671b9fae', 13 13 'core.pkg.js' => 'c783d8f6', 14 14 'differential.pkg.css' => '8d8360fb', 15 15 'differential.pkg.js' => '67e02996', ··· 134 134 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e', 135 135 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'f14f2422', 136 136 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46', 137 - 'rsrc/css/phui/phui-action-list.css' => 'c4972757', 137 + 'rsrc/css/phui/phui-action-list.css' => 'c34af376', 138 138 'rsrc/css/phui/phui-action-panel.css' => '6c386cbf', 139 139 'rsrc/css/phui/phui-badge.css' => '666e25ad', 140 140 'rsrc/css/phui/phui-basic-nav-view.css' => '56ebd66d', ··· 757 757 'path-typeahead' => 'ad486db3', 758 758 'people-picture-menu-item-css' => 'fe8e07cf', 759 759 'people-profile-css' => '2ea2daa1', 760 - 'phabricator-action-list-view-css' => 'c4972757', 760 + 'phabricator-action-list-view-css' => 'c34af376', 761 761 'phabricator-busy' => '5202e831', 762 762 'phabricator-chatlog-css' => 'abdc76ee', 763 763 'phabricator-content-source-view-css' => 'cdf0d579',
+4
src/__phutil_library_map__.php
··· 2942 2942 'PhabricatorDashboardPanelRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php', 2943 2943 'PhabricatorDashboardPanelSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php', 2944 2944 'PhabricatorDashboardPanelStatusTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelStatusTransaction.php', 2945 + 'PhabricatorDashboardPanelTabsController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php', 2945 2946 'PhabricatorDashboardPanelTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPanelTransaction.php', 2946 2947 'PhabricatorDashboardPanelTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php', 2947 2948 'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php', ··· 2984 2985 'PhabricatorDashboardRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php', 2985 2986 'PhabricatorDashboardSchemaSpec' => 'applications/dashboard/storage/PhabricatorDashboardSchemaSpec.php', 2986 2987 'PhabricatorDashboardSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardSearchEngine.php', 2988 + 'PhabricatorDashboardTabsPanelTabsTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardTabsPanelTabsTransaction.php', 2987 2989 'PhabricatorDashboardTabsPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php', 2988 2990 'PhabricatorDashboardTextPanelTextTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardTextPanelTextTransaction.php', 2989 2991 'PhabricatorDashboardTextPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php', ··· 8920 8922 'PhabricatorDashboardPanelRenderingEngine' => 'Phobject', 8921 8923 'PhabricatorDashboardPanelSearchEngine' => 'PhabricatorApplicationSearchEngine', 8922 8924 'PhabricatorDashboardPanelStatusTransaction' => 'PhabricatorDashboardPanelTransactionType', 8925 + 'PhabricatorDashboardPanelTabsController' => 'PhabricatorDashboardController', 8923 8926 'PhabricatorDashboardPanelTransaction' => 'PhabricatorModularTransaction', 8924 8927 'PhabricatorDashboardPanelTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 8925 8928 'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery', ··· 8967 8970 'PhabricatorDashboardRenderingEngine' => 'Phobject', 8968 8971 'PhabricatorDashboardSchemaSpec' => 'PhabricatorConfigSchemaSpec', 8969 8972 'PhabricatorDashboardSearchEngine' => 'PhabricatorApplicationSearchEngine', 8973 + 'PhabricatorDashboardTabsPanelTabsTransaction' => 'PhabricatorDashboardPanelPropertyTransaction', 8970 8974 'PhabricatorDashboardTabsPanelType' => 'PhabricatorDashboardPanelType', 8971 8975 'PhabricatorDashboardTextPanelTextTransaction' => 'PhabricatorDashboardPanelPropertyTransaction', 8972 8976 'PhabricatorDashboardTextPanelType' => 'PhabricatorDashboardPanelType',
+2
src/applications/dashboard/application/PhabricatorDashboardApplication.php
··· 62 62 'render/(?P<id>\d+)/' => 'PhabricatorDashboardPanelRenderController', 63 63 'archive/(?P<id>\d+)/' 64 64 => 'PhabricatorDashboardPanelArchiveController', 65 + 'tabs/(?P<id>\d+)/(?P<op>add|move|remove|rename)/' 66 + => 'PhabricatorDashboardPanelTabsController', 65 67 ), 66 68 ), 67 69 '/portal/' => array(
+295
src/applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php
··· 1 + <?php 2 + 3 + final class PhabricatorDashboardPanelTabsController 4 + extends PhabricatorDashboardController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $this->getViewer(); 8 + 9 + $panel = id(new PhabricatorDashboardPanelQuery()) 10 + ->setViewer($viewer) 11 + ->withIDs(array($request->getURIData('id'))) 12 + ->requireCapabilities( 13 + array( 14 + PhabricatorPolicyCapability::CAN_VIEW, 15 + PhabricatorPolicyCapability::CAN_EDIT, 16 + )) 17 + ->executeOne(); 18 + if (!$panel) { 19 + return new Aphront404Response(); 20 + } 21 + 22 + $tabs_type = id(new PhabricatorDashboardTabsPanelType()) 23 + ->getPanelTypeKey(); 24 + 25 + // This controller may only be used to edit tab panels. 26 + $panel_type = $panel->getPanelType(); 27 + if ($panel_type !== $tabs_type) { 28 + return new Aphront404Response(); 29 + } 30 + 31 + $op = $request->getURIData('op'); 32 + $after = $request->getStr('after'); 33 + if (!strlen($after)) { 34 + $after = null; 35 + } 36 + 37 + $target = $request->getStr('target'); 38 + if (!strlen($target)) { 39 + $target = null; 40 + } 41 + 42 + $impl = $panel->getImplementation(); 43 + $config = $impl->getPanelConfiguration($panel); 44 + 45 + $cancel_uri = $panel->getURI(); 46 + 47 + if ($after !== null) { 48 + $found = false; 49 + foreach ($config as $key => $spec) { 50 + if ((string)$key === $after) { 51 + $found = true; 52 + break; 53 + } 54 + } 55 + 56 + if (!$found) { 57 + return $this->newDialog() 58 + ->setTitle(pht('Adjacent Tab Not Found')) 59 + ->appendParagraph( 60 + pht( 61 + 'Adjacent tab ("%s") was not found on this panel. It may have '. 62 + 'been removed.', 63 + $after)) 64 + ->addCancelButton($cancel_uri); 65 + } 66 + } 67 + 68 + if ($target !== null) { 69 + $found = false; 70 + foreach ($config as $key => $spec) { 71 + if ((string)$key === $target) { 72 + $found = true; 73 + break; 74 + } 75 + } 76 + 77 + if (!$found) { 78 + return $this->newDialog() 79 + ->setTitle(pht('Target Tab Not Found')) 80 + ->appendParagraph( 81 + pht( 82 + 'Target tab ("%s") was not found on this panel. It may have '. 83 + 'been removed.', 84 + $target)) 85 + ->addCancelButton($cancel_uri); 86 + } 87 + } 88 + 89 + switch ($op) { 90 + case 'add': 91 + return $this->handleAddOperation($panel, $after, $cancel_uri); 92 + case 'remove': 93 + return $this->handleRemoveOperation($panel, $target, $cancel_uri); 94 + case 'move': 95 + break; 96 + case 'rename': 97 + return $this->handleRenameOperation($panel, $target, $cancel_uri); 98 + } 99 + } 100 + 101 + private function handleAddOperation( 102 + PhabricatorDashboardPanel $panel, 103 + $after, 104 + $cancel_uri) { 105 + $request = $this->getRequest(); 106 + $viewer = $this->getViewer(); 107 + 108 + $panel_phid = null; 109 + $errors = array(); 110 + if ($request->isFormPost()) { 111 + $panel_phid = $request->getArr('panelPHID'); 112 + $panel_phid = head($panel_phid); 113 + 114 + $add_panel = id(new PhabricatorDashboardPanelQuery()) 115 + ->setViewer($viewer) 116 + ->withPHIDs(array($panel_phid)) 117 + ->executeOne(); 118 + if (!$add_panel) { 119 + $errors[] = pht('You must select a valid panel.'); 120 + } 121 + 122 + if (!$errors) { 123 + $add_panel_config = array( 124 + 'name' => null, 125 + 'panelID' => $add_panel->getID(), 126 + ); 127 + $add_panel_key = Filesystem::readRandomCharacters(12); 128 + 129 + $impl = $panel->getImplementation(); 130 + $old_config = $impl->getPanelConfiguration($panel); 131 + $new_config = array(); 132 + if ($after === null) { 133 + $new_config = $old_config; 134 + $new_config[] = $add_panel_config; 135 + } else { 136 + foreach ($old_config as $key => $value) { 137 + $new_config[$key] = $value; 138 + if ((string)$key === $after) { 139 + $new_config[$add_panel_key] = $add_panel_config; 140 + } 141 + } 142 + } 143 + 144 + $xactions = array(); 145 + 146 + $xactions[] = $panel->getApplicationTransactionTemplate() 147 + ->setTransactionType( 148 + PhabricatorDashboardTabsPanelTabsTransaction::TRANSACTIONTYPE) 149 + ->setNewValue($new_config); 150 + 151 + $editor = id(new PhabricatorDashboardPanelTransactionEditor()) 152 + ->setContentSourceFromRequest($request) 153 + ->setActor($viewer) 154 + ->setContinueOnNoEffect(true) 155 + ->setContinueOnMissingFields(true); 156 + 157 + $editor->applyTransactions($panel, $xactions); 158 + 159 + return id(new AphrontRedirectResponse())->setURI($cancel_uri); 160 + } 161 + } 162 + 163 + if ($panel_phid) { 164 + $v_panel = array($panel_phid); 165 + } else { 166 + $v_panel = array(); 167 + } 168 + 169 + $form = id(new AphrontFormView()) 170 + ->setViewer($viewer) 171 + ->appendControl( 172 + id(new AphrontFormTokenizerControl()) 173 + ->setDatasource(new PhabricatorDashboardPanelDatasource()) 174 + ->setLimit(1) 175 + ->setName('panelPHID') 176 + ->setLabel(pht('Panel')) 177 + ->setValue($v_panel)); 178 + 179 + return $this->newDialog() 180 + ->setTitle(pht('Choose Dashboard Panel')) 181 + ->setErrors($errors) 182 + ->setWidth(AphrontDialogView::WIDTH_FORM) 183 + ->addHiddenInput('after', $after) 184 + ->appendForm($form) 185 + ->addCancelButton($cancel_uri) 186 + ->addSubmitButton(pht('Add Panel')); 187 + } 188 + 189 + private function handleRemoveOperation( 190 + PhabricatorDashboardPanel $panel, 191 + $target, 192 + $cancel_uri) { 193 + $request = $this->getRequest(); 194 + $viewer = $this->getViewer(); 195 + 196 + $panel_phid = null; 197 + $errors = array(); 198 + if ($request->isFormPost()) { 199 + $impl = $panel->getImplementation(); 200 + $old_config = $impl->getPanelConfiguration($panel); 201 + 202 + $new_config = $this->removePanel($old_config, $target); 203 + $this->writePanelConfig($panel, $new_config); 204 + 205 + return id(new AphrontRedirectResponse())->setURI($cancel_uri); 206 + } 207 + 208 + return $this->newDialog() 209 + ->setTitle(pht('Remove tab?')) 210 + ->addHiddenInput('target', $target) 211 + ->appendParagraph(pht('Really remove this tab?')) 212 + ->addCancelButton($cancel_uri) 213 + ->addSubmitButton(pht('Remove Tab')); 214 + } 215 + 216 + private function handleRenameOperation( 217 + PhabricatorDashboardPanel $panel, 218 + $target, 219 + $cancel_uri) { 220 + $request = $this->getRequest(); 221 + $viewer = $this->getViewer(); 222 + 223 + $impl = $panel->getImplementation(); 224 + $old_config = $impl->getPanelConfiguration($panel); 225 + 226 + $spec = $old_config[$target]; 227 + $name = idx($spec, 'name'); 228 + 229 + if ($request->isFormPost()) { 230 + $name = $request->getStr('name'); 231 + 232 + $new_config = $this->renamePanel($old_config, $target, $name); 233 + $this->writePanelConfig($panel, $new_config); 234 + 235 + return id(new AphrontRedirectResponse())->setURI($cancel_uri); 236 + } 237 + 238 + $form = id(new AphrontFormView()) 239 + ->setViewer($viewer) 240 + ->appendControl( 241 + id(new AphrontFormTextControl()) 242 + ->setValue($name) 243 + ->setName('name') 244 + ->setLabel(pht('Tab Name'))); 245 + 246 + return $this->newDialog() 247 + ->setTitle(pht('Rename Panel')) 248 + ->addHiddenInput('target', $target) 249 + ->appendForm($form) 250 + ->addCancelButton($cancel_uri) 251 + ->addSubmitButton(pht('Rename Tab')); 252 + } 253 + 254 + 255 + private function writePanelConfig( 256 + PhabricatorDashboardPanel $panel, 257 + array $config) { 258 + $request = $this->getRequest(); 259 + $viewer = $this->getViewer(); 260 + 261 + $xactions = array(); 262 + 263 + $xactions[] = $panel->getApplicationTransactionTemplate() 264 + ->setTransactionType( 265 + PhabricatorDashboardTabsPanelTabsTransaction::TRANSACTIONTYPE) 266 + ->setNewValue($config); 267 + 268 + $editor = id(new PhabricatorDashboardPanelTransactionEditor()) 269 + ->setContentSourceFromRequest($request) 270 + ->setActor($viewer) 271 + ->setContinueOnNoEffect(true) 272 + ->setContinueOnMissingFields(true); 273 + 274 + return $editor->applyTransactions($panel, $xactions); 275 + } 276 + 277 + private function removePanel(array $config, $target) { 278 + $result = array(); 279 + 280 + foreach ($config as $key => $panel_spec) { 281 + if ((string)$key === $target) { 282 + continue; 283 + } 284 + $result[$key] = $panel_spec; 285 + } 286 + 287 + return $result; 288 + } 289 + 290 + private function renamePanel(array $config, $target, $name) { 291 + $config[$target]['name'] = $name; 292 + return $config; 293 + } 294 + 295 + }
+4
src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
··· 43 43 return $this->panelHandle; 44 44 } 45 45 46 + public function isEditMode() { 47 + return ($this->getHeaderMode() === self::HEADER_MODE_EDIT); 48 + } 49 + 46 50 /** 47 51 * Allow the engine to render the panel via Ajax. 48 52 */
+172 -22
src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php
··· 20 20 } 21 21 22 22 protected function newEditEngineFields(PhabricatorDashboardPanel $panel) { 23 - // TODO: Restore this using EditEngine instead of CustomField. 24 23 return array(); 25 24 } 26 25 ··· 29 28 return false; 30 29 } 31 30 31 + public function getPanelConfiguration(PhabricatorDashboardPanel $panel) { 32 + $config = $panel->getProperty('config'); 33 + 34 + if (!is_array($config)) { 35 + // NOTE: The older version of this panel stored raw JSON. 36 + try { 37 + $config = phutil_json_decode($config); 38 + } catch (PhutilJSONParserException $ex) { 39 + $config = array(); 40 + } 41 + } 42 + 43 + return $config; 44 + } 45 + 32 46 public function renderPanelContent( 33 47 PhabricatorUser $viewer, 34 48 PhabricatorDashboardPanel $panel, 35 49 PhabricatorDashboardPanelRenderingEngine $engine) { 36 50 37 - $config = $panel->getProperty('config'); 38 - if (!is_array($config)) { 39 - // NOTE: The older version of this panel stored raw JSON. 40 - $config = phutil_json_decode($config); 41 - } 51 + $is_edit = $engine->isEditMode(); 52 + $config = $this->getPanelConfiguration($panel); 42 53 43 54 $list = id(new PHUIListView()) 44 55 ->setType(PHUIListView::NAVBAR_LIST); 45 - 46 - $selected = 0; 47 56 48 57 $node_ids = array(); 49 58 foreach ($config as $idx => $tab_spec) { 50 59 $node_ids[$idx] = celerity_generate_unique_node_id(); 51 60 } 52 61 53 - foreach ($config as $idx => $tab_spec) { 54 - $list->addMenuItem( 55 - id(new PHUIListItemView()) 56 - ->setHref('#') 57 - ->setSelected($idx == $selected) 58 - ->addSigil('dashboard-tab-panel-tab') 59 - ->setMetadata(array('idx' => $idx)) 60 - ->setName(idx($tab_spec, 'name', pht('Nameless Tab')))); 61 - } 62 - 63 62 $ids = ipull($config, 'panelID'); 64 63 if ($ids) { 65 64 $panels = id(new PhabricatorDashboardPanelQuery()) ··· 70 69 $panels = array(); 71 70 } 72 71 72 + $id = $panel->getID(); 73 + 74 + $add_uri = urisprintf('/dashboard/panel/tabs/%d/add/', $id); 75 + $add_uri = new PhutilURI($add_uri); 76 + 77 + $remove_uri = urisprintf('/dashboard/panel/tabs/%d/remove/', $id); 78 + $remove_uri = new PhutilURI($remove_uri); 79 + 80 + $rename_uri = urisprintf('/dashboard/panel/tabs/%d/rename/', $id); 81 + $rename_uri = new PhutilURI($rename_uri); 82 + 83 + $selected = 0; 84 + 85 + $last_idx = null; 86 + foreach ($config as $idx => $tab_spec) { 87 + $panel_id = idx($tab_spec, 'panelID'); 88 + $subpanel = idx($panels, $panel_id); 89 + 90 + $name = idx($tab_spec, 'name'); 91 + if (!strlen($name)) { 92 + if ($subpanel) { 93 + $name = $subpanel->getName(); 94 + } 95 + } 96 + 97 + if (!strlen($name)) { 98 + $name = pht('Unnamed Tab'); 99 + } 100 + 101 + $tab_view = id(new PHUIListItemView()) 102 + ->setHref('#') 103 + ->setSelected($idx == $selected) 104 + ->addSigil('dashboard-tab-panel-tab') 105 + ->setMetadata(array('idx' => $idx)) 106 + ->setName($name); 107 + 108 + if ($is_edit) { 109 + $dropdown_menu = id(new PhabricatorActionListView()) 110 + ->setViewer($viewer); 111 + 112 + $remove_tab_uri = id(clone $remove_uri) 113 + ->replaceQueryParam('target', $idx); 114 + 115 + $rename_tab_uri = id(clone $rename_uri) 116 + ->replaceQueryParam('target', $idx); 117 + 118 + if ($subpanel) { 119 + $details_uri = $subpanel->getURI(); 120 + } else { 121 + $details_uri = null; 122 + } 123 + 124 + $edit_uri = urisprintf( 125 + '/dashboard/panel/edit/%d/', 126 + $panel_id); 127 + if ($subpanel) { 128 + $can_edit = PhabricatorPolicyFilter::hasCapability( 129 + $viewer, 130 + $subpanel, 131 + PhabricatorPolicyCapability::CAN_EDIT); 132 + } else { 133 + $can_edit = false; 134 + } 135 + 136 + $dropdown_menu->addAction( 137 + id(new PhabricatorActionView()) 138 + ->setName(pht('Rename Tab')) 139 + ->setIcon('fa-pencil') 140 + ->setHref($rename_tab_uri) 141 + ->setWorkflow(true)); 142 + 143 + $dropdown_menu->addAction( 144 + id(new PhabricatorActionView()) 145 + ->setName(pht('Remove Tab')) 146 + ->setIcon('fa-times') 147 + ->setHref($remove_tab_uri) 148 + ->setWorkflow(true)); 149 + 150 + $dropdown_menu->addAction( 151 + id(new PhabricatorActionView()) 152 + ->setType(PhabricatorActionView::TYPE_DIVIDER)); 153 + 154 + $dropdown_menu->addAction( 155 + id(new PhabricatorActionView()) 156 + ->setName(pht('Edit Panel')) 157 + ->setIcon('fa-pencil') 158 + ->setHref($edit_uri) 159 + ->setWorkflow(true) 160 + ->setDisabled(!$can_edit)); 161 + 162 + $dropdown_menu->addAction( 163 + id(new PhabricatorActionView()) 164 + ->setName(pht('View Panel Details')) 165 + ->setIcon('fa-window-maximize') 166 + ->setHref($details_uri) 167 + ->setDisabled(!$subpanel)); 168 + 169 + $tab_view->setDropdownMenu($dropdown_menu); 170 + } 171 + 172 + $list->addMenuItem($tab_view); 173 + 174 + $last_idx = $idx; 175 + } 176 + 177 + if ($is_edit) { 178 + $actions = id(new PhabricatorActionListView()) 179 + ->setViewer($viewer); 180 + 181 + $add_last_uri = clone $add_uri; 182 + if ($last_idx) { 183 + $add_last_uri->replaceQueryParam('after', $last_idx); 184 + } 185 + 186 + $actions->addAction( 187 + id(new PhabricatorActionView()) 188 + ->setName(pht('Add Existing Panel')) 189 + ->setIcon('fa-window-maximize') 190 + ->setHref($add_last_uri) 191 + ->setWorkflow(true)); 192 + 193 + $list->addMenuItem( 194 + id(new PHUIListItemView()) 195 + ->setHref('#') 196 + ->setSelected(false) 197 + ->setName(pht('Add Tab...')) 198 + ->setDropdownMenu($actions)); 199 + } 200 + 73 201 $parent_phids = $engine->getParentPanelPHIDs(); 74 202 $parent_phids[] = $panel->getPHID(); 75 203 ··· 83 211 $no_headers = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_NONE; 84 212 foreach ($config as $idx => $tab_spec) { 85 213 $panel_id = idx($tab_spec, 'panelID'); 86 - $panel = idx($panels, $panel_id); 214 + $subpanel = idx($panels, $panel_id); 87 215 88 - if ($panel) { 216 + if ($subpanel) { 89 217 $panel_content = id(new PhabricatorDashboardPanelRenderingEngine()) 90 218 ->setViewer($viewer) 91 219 ->setEnableAsyncRendering(true) 92 220 ->setParentPanelPHIDs($parent_phids) 93 - ->setPanel($panel) 94 - ->setPanelPHID($panel->getPHID()) 221 + ->setPanel($subpanel) 222 + ->setPanelPHID($subpanel->getPHID()) 95 223 ->setHeaderMode($no_headers) 96 224 ->setMovable(false) 97 225 ->renderPanel(); ··· 106 234 'style' => ($idx == $selected) ? null : 'display: none', 107 235 ), 108 236 $panel_content); 237 + } 238 + 239 + if (!$content) { 240 + if ($is_edit) { 241 + $message = pht( 242 + 'This tab panel does not have any tabs yet. Use "Add Tab" to '. 243 + 'create or place a tab.'); 244 + } else { 245 + $message = pht( 246 + 'This tab panel does not have any tabs yet.'); 247 + } 248 + 249 + $content = id(new PHUIInfoView()) 250 + ->setSeverity(PHUIInfoView::SEVERITY_NODATA) 251 + ->setErrors( 252 + array( 253 + $message, 254 + )); 255 + 256 + $content = id(new PHUIBoxView()) 257 + ->addClass('mlt mlb') 258 + ->appendChild($content); 109 259 } 110 260 111 261 Javelin::initBehavior('dashboard-tab-panel');
+3 -3
src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php
··· 48 48 $type_text = nonempty($panel->getPanelType(), pht('Unknown Type')); 49 49 $icon = 'fa-question'; 50 50 } 51 - $id = $panel->getID(); 51 + $phid = $panel->getPHID(); 52 52 $monogram = $panel->getMonogram(); 53 53 $properties = $panel->getProperties(); 54 54 55 55 $result = id(new PhabricatorTypeaheadResult()) 56 56 ->setName($monogram.' '.$panel->getName()) 57 - ->setPHID($id) 57 + ->setPHID($phid) 58 58 ->setIcon($icon) 59 59 ->addAttribute($type_text); 60 60 ··· 66 66 $result->setClosed(pht('Archived')); 67 67 } 68 68 69 - $results[$id] = $result; 69 + $results[$phid] = $result; 70 70 } 71 71 72 72 return $results;
+12
src/applications/dashboard/xaction/panel/PhabricatorDashboardTabsPanelTabsTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorDashboardTabsPanelTabsTransaction 4 + extends PhabricatorDashboardPanelPropertyTransaction { 5 + 6 + const TRANSACTIONTYPE = 'tabs.tabs'; 7 + 8 + protected function getPropertyKey() { 9 + return 'config'; 10 + } 11 + 12 + }
+13
src/view/phui/PHUIListItemView.php
··· 35 35 private $actionIconHref; 36 36 private $count; 37 37 private $rel; 38 + private $hasDropdown; 38 39 39 40 public function setOpenInNewWindow($open_in_new_window) { 40 41 $this->openInNewWindow = $open_in_new_window; ··· 68 69 69 70 $this->addSigil('phui-dropdown-menu'); 70 71 $this->setMetadata($actions->getDropdownMenuMetadata()); 72 + $this->hasDropdown = true; 71 73 72 74 return $this; 73 75 } ··· 235 237 $classes[] = 'phui-list-item-has-action-icon'; 236 238 } 237 239 240 + if ($this->hasDropdown) { 241 + $classes[] = 'dropdown'; 242 + } 243 + 238 244 return array( 239 245 'class' => implode(' ', $classes), 240 246 ); ··· 363 369 $this->count); 364 370 } 365 371 372 + if ($this->hasDropdown) { 373 + $caret = phutil_tag('span', array('class' => 'caret'), ''); 374 + } else { 375 + $caret = null; 376 + } 377 + 366 378 $icons = $this->getIcons(); 367 379 368 380 $list_item = javelin_tag( ··· 381 393 $icons, 382 394 $this->renderChildren(), 383 395 $name, 396 + $caret, 384 397 $count, 385 398 )); 386 399
+11
webroot/rsrc/css/phui/phui-action-list.css
··· 213 213 .phabricator-action-view-item .phui-icon-view { 214 214 color: {$sky}; 215 215 } 216 + 217 + .phui-list-item-view.dropdown .phui-list-item-href { 218 + padding-right: 28px; 219 + } 220 + 221 + .phui-list-item-view .caret { 222 + position: absolute; 223 + top: 6px; 224 + right: 12px; 225 + border-top: 7px solid {$greytext}; 226 + }