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

Introduce "Curtain" views, panels, and extensions

Summary:
This opens up the new action column to have specialized rendering and behavior. Briefly:

- Converted applications (right now, only Paste) render a `CurtainView` to build the column content.
- This view uses new extensions to build panels (projects, subscribers, tokens).
- The panel extension code and rendering can be changed without breaking old stuff.

Minor changes:

- Token awards now load their tokens, for consistency/simplicity.
- Removed the rest of the "fork of" / "forked from" UI in Paste -- I essentially removed these features a while ago, and no one has complained.

Test Plan:
UI is a bit rough, but works, and it's going to get changed now anyway:

{F1160550}

{F1160551}

Reviewers: chad

Reviewed By: chad

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

+586 -73
+2
resources/celerity/map.php
··· 127 127 'rsrc/css/phui/phui-button.css' => 'a64a8de6', 128 128 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 129 129 'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5', 130 + 'rsrc/css/phui/phui-curtain-view.css' => '8bb7ee8f', 130 131 'rsrc/css/phui/phui-document-pro.css' => '92d5b648', 131 132 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 132 133 'rsrc/css/phui/phui-document.css' => '9c71d2bf', ··· 811 812 'phui-calendar-month-css' => '476be7e0', 812 813 'phui-chart-css' => '6bf6f78e', 813 814 'phui-crumbs-view-css' => '79d536e5', 815 + 'phui-curtain-view-css' => '8bb7ee8f', 814 816 'phui-document-summary-view-css' => '9ca48bdf', 815 817 'phui-document-view-css' => '9c71d2bf', 816 818 'phui-document-view-pro-css' => '92d5b648',
+12
src/__phutil_library_map__.php
··· 1505 1505 'PHUIColorPalletteExample' => 'applications/uiexample/examples/PHUIColorPalletteExample.php', 1506 1506 'PHUICrumbView' => 'view/phui/PHUICrumbView.php', 1507 1507 'PHUICrumbsView' => 'view/phui/PHUICrumbsView.php', 1508 + 'PHUICurtainExtension' => 'view/extension/PHUICurtainExtension.php', 1509 + 'PHUICurtainPanelView' => 'view/layout/PHUICurtainPanelView.php', 1510 + 'PHUICurtainView' => 'view/layout/PHUICurtainView.php', 1508 1511 'PHUIDiffInlineCommentDetailView' => 'infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php', 1509 1512 'PHUIDiffInlineCommentEditView' => 'infrastructure/diff/view/PHUIDiffInlineCommentEditView.php', 1510 1513 'PHUIDiffInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php', ··· 3011 3014 'PhabricatorProjectWatcherListView' => 'applications/project/view/PhabricatorProjectWatcherListView.php', 3012 3015 'PhabricatorProjectWorkboardBackgroundColor' => 'applications/project/constants/PhabricatorProjectWorkboardBackgroundColor.php', 3013 3016 'PhabricatorProjectWorkboardProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php', 3017 + 'PhabricatorProjectsCurtainExtension' => 'applications/project/engineextension/PhabricatorProjectsCurtainExtension.php', 3014 3018 'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php', 3015 3019 'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php', 3016 3020 'PhabricatorProjectsFulltextEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php', ··· 3320 3324 'PhabricatorSubscriptionsAddSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php', 3321 3325 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php', 3322 3326 'PhabricatorSubscriptionsApplication' => 'applications/subscriptions/application/PhabricatorSubscriptionsApplication.php', 3327 + 'PhabricatorSubscriptionsCurtainExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php', 3323 3328 'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php', 3324 3329 'PhabricatorSubscriptionsEditEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php', 3325 3330 'PhabricatorSubscriptionsEditor' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php', ··· 3388 3393 'PhabricatorTokenUIEventListener' => 'applications/tokens/event/PhabricatorTokenUIEventListener.php', 3389 3394 'PhabricatorTokenizerEditField' => 'applications/transactions/editfield/PhabricatorTokenizerEditField.php', 3390 3395 'PhabricatorTokensApplication' => 'applications/tokens/application/PhabricatorTokensApplication.php', 3396 + 'PhabricatorTokensCurtainExtension' => 'applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php', 3391 3397 'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php', 3392 3398 'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php', 3393 3399 'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php', ··· 5751 5757 'PHUIColorPalletteExample' => 'PhabricatorUIExample', 5752 5758 'PHUICrumbView' => 'AphrontView', 5753 5759 'PHUICrumbsView' => 'AphrontView', 5760 + 'PHUICurtainExtension' => 'Phobject', 5761 + 'PHUICurtainPanelView' => 'AphrontTagView', 5762 + 'PHUICurtainView' => 'AphrontTagView', 5754 5763 'PHUIDiffInlineCommentDetailView' => 'PHUIDiffInlineCommentView', 5755 5764 'PHUIDiffInlineCommentEditView' => 'PHUIDiffInlineCommentView', 5756 5765 'PHUIDiffInlineCommentRowScaffold' => 'AphrontView', ··· 7503 7512 'PhabricatorProjectWatcherListView' => 'PhabricatorProjectUserListView', 7504 7513 'PhabricatorProjectWorkboardBackgroundColor' => 'Phobject', 7505 7514 'PhabricatorProjectWorkboardProfilePanel' => 'PhabricatorProfilePanel', 7515 + 'PhabricatorProjectsCurtainExtension' => 'PHUICurtainExtension', 7506 7516 'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension', 7507 7517 'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField', 7508 7518 'PhabricatorProjectsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', ··· 7872 7882 'PhabricatorSubscriptionsAddSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 7873 7883 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 7874 7884 'PhabricatorSubscriptionsApplication' => 'PhabricatorApplication', 7885 + 'PhabricatorSubscriptionsCurtainExtension' => 'PHUICurtainExtension', 7875 7886 'PhabricatorSubscriptionsEditController' => 'PhabricatorController', 7876 7887 'PhabricatorSubscriptionsEditEngineExtension' => 'PhabricatorEditEngineExtension', 7877 7888 'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor', ··· 7945 7956 'PhabricatorTokenUIEventListener' => 'PhabricatorEventListener', 7946 7957 'PhabricatorTokenizerEditField' => 'PhabricatorPHIDListEditField', 7947 7958 'PhabricatorTokensApplication' => 'PhabricatorApplication', 7959 + 'PhabricatorTokensCurtainExtension' => 'PHUICurtainExtension', 7948 7960 'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel', 7949 7961 'PhabricatorTooltipUIExample' => 'PhabricatorUIExample', 7950 7962 'PhabricatorTransactions' => 'Phobject',
+20 -1
src/applications/base/controller/PhabricatorController.php
··· 468 468 469 469 public function newApplicationMenu() { 470 470 return id(new PHUIApplicationMenuView()) 471 - ->setViewer($this->getRequest()->getUser()); 471 + ->setViewer($this->getViewer()); 472 + } 473 + 474 + public function newCurtainView($object) { 475 + $viewer = $this->getViewer(); 476 + 477 + $action_list = id(new PhabricatorActionListView()) 478 + ->setViewer($viewer) 479 + ->setObject($object); 480 + 481 + $curtain = id(new PHUICurtainView()) 482 + ->setViewer($viewer) 483 + ->setActionList($action_list); 484 + 485 + $panels = PHUICurtainExtension::buildExtensionPanels($viewer, $object); 486 + foreach ($panels as $panel) { 487 + $curtain->addPanel($panel); 488 + } 489 + 490 + return $curtain; 472 491 } 473 492 474 493 protected function buildTransactionTimeline(
+33 -69
src/applications/paste/controller/PhabricatorPasteViewController.php
··· 40 40 return new Aphront404Response(); 41 41 } 42 42 43 - $forks = id(new PhabricatorPasteQuery()) 44 - ->setViewer($viewer) 45 - ->withParentPHIDs(array($paste->getPHID())) 46 - ->execute(); 47 - $fork_phids = mpull($forks, 'getPHID'); 43 + $header = $this->buildHeaderView($paste); 44 + $curtain = $this->buildCurtain($paste); 48 45 49 - $header = $this->buildHeaderView($paste); 50 - $actions = $this->buildActionView($viewer, $paste); 51 - $properties = $this->buildPropertyView($paste, $fork_phids); 52 46 $subheader = $this->buildSubheaderView($paste); 53 47 $source_code = $this->buildSourceCodeView($paste, $this->highlightMap); 54 48 ··· 78 72 $timeline, 79 73 $comment_view, 80 74 )) 81 - ->setPropertyList($properties) 82 - ->setActionList($actions) 75 + ->setCurtain($curtain) 83 76 ->addClass('ponder-question-view'); 84 77 85 78 return $this->newPage() ··· 116 109 return $header; 117 110 } 118 111 119 - private function buildActionView( 120 - PhabricatorUser $viewer, 121 - PhabricatorPaste $paste) { 112 + private function buildCurtain(PhabricatorPaste $paste) { 113 + $viewer = $this->getViewer(); 114 + $curtain = $this->newCurtainView($paste); 122 115 123 116 $can_edit = PhabricatorPolicyFilter::hasCapability( 124 117 $viewer, ··· 126 119 PhabricatorPolicyCapability::CAN_EDIT); 127 120 128 121 $id = $paste->getID(); 122 + $edit_uri = $this->getApplicationURI("edit/{$id}/"); 123 + $archive_uri = $this->getApplicationURI("archive/{$id}/"); 124 + $raw_uri = $this->getApplicationURI("raw/{$id}/"); 129 125 130 - $action_list = id(new PhabricatorActionListView()) 131 - ->setUser($viewer) 132 - ->setObject($paste); 126 + $curtain->addAction( 127 + id(new PhabricatorActionView()) 128 + ->setName(pht('Edit Paste')) 129 + ->setIcon('fa-pencil') 130 + ->setDisabled(!$can_edit) 131 + ->setHref($edit_uri)); 133 132 134 - $action_list->addAction( 133 + if ($paste->isArchived()) { 134 + $curtain->addAction( 135 135 id(new PhabricatorActionView()) 136 - ->setName(pht('Edit Paste')) 137 - ->setIcon('fa-pencil') 136 + ->setName(pht('Activate Paste')) 137 + ->setIcon('fa-check') 138 138 ->setDisabled(!$can_edit) 139 - ->setHref($this->getApplicationURI("edit/{$id}/"))); 140 - 141 - if ($paste->isArchived()) { 142 - $action_list->addAction( 143 - id(new PhabricatorActionView()) 144 - ->setName(pht('Activate Paste')) 145 - ->setIcon('fa-check') 146 - ->setDisabled(!$can_edit) 147 - ->setWorkflow($can_edit) 148 - ->setHref($this->getApplicationURI("archive/{$id}/"))); 139 + ->setWorkflow($can_edit) 140 + ->setHref($archive_uri)); 149 141 } else { 150 - $action_list->addAction( 142 + $curtain->addAction( 151 143 id(new PhabricatorActionView()) 152 - ->setName(pht('Archive Paste')) 153 - ->setIcon('fa-ban') 154 - ->setDisabled(!$can_edit) 155 - ->setWorkflow($can_edit) 156 - ->setHref($this->getApplicationURI("archive/{$id}/"))); 144 + ->setName(pht('Archive Paste')) 145 + ->setIcon('fa-ban') 146 + ->setDisabled(!$can_edit) 147 + ->setWorkflow($can_edit) 148 + ->setHref($archive_uri)); 157 149 } 158 150 159 - $action_list->addAction( 160 - id(new PhabricatorActionView()) 161 - ->setName(pht('View Raw File')) 162 - ->setIcon('fa-file-text-o') 163 - ->setHref($this->getApplicationURI("raw/{$id}/"))); 151 + $curtain->addAction( 152 + id(new PhabricatorActionView()) 153 + ->setName(pht('View Raw File')) 154 + ->setIcon('fa-file-text-o') 155 + ->setHref($raw_uri)); 164 156 165 - return $action_list; 157 + return $curtain; 166 158 } 167 159 168 160 ··· 189 181 ->setImage($image_uri) 190 182 ->setImageHref($image_href) 191 183 ->setContent($content); 192 - } 193 - 194 - private function buildPropertyView( 195 - PhabricatorPaste $paste, 196 - array $child_phids) { 197 - $viewer = $this->getViewer(); 198 - 199 - $properties = id(new PHUIPropertyListView()) 200 - ->setUser($viewer) 201 - ->setObject($paste); 202 - 203 - if ($paste->getParentPHID()) { 204 - $properties->addProperty( 205 - pht('Forked From'), 206 - $viewer->renderHandle($paste->getParentPHID())); 207 - } 208 - 209 - if ($child_phids) { 210 - $properties->addProperty( 211 - pht('Forks'), 212 - $viewer->renderHandleList($child_phids)); 213 - } 214 - 215 - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( 216 - $viewer, 217 - $paste); 218 - 219 - return $properties; 220 184 } 221 185 222 186 }
+91
src/applications/project/engineextension/PhabricatorProjectsCurtainExtension.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectsCurtainExtension 4 + extends PHUICurtainExtension { 5 + 6 + const EXTENSIONKEY = 'projects.projects'; 7 + 8 + public function shouldEnableForObject($object) { 9 + return ($object instanceof PhabricatorProjectInterface); 10 + } 11 + 12 + public function getExtensionApplication() { 13 + return new PhabricatorProjectApplication(); 14 + } 15 + 16 + public function buildCurtainPanel($object) { 17 + $viewer = $this->getViewer(); 18 + 19 + $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( 20 + $object->getPHID(), 21 + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); 22 + 23 + $has_projects = (bool)$project_phids; 24 + $project_phids = array_reverse($project_phids); 25 + $handles = $viewer->loadHandles($project_phids); 26 + 27 + // If this object can appear on boards, build the workboard annotations. 28 + // Some day, this might be a generic interface. For now, only tasks can 29 + // appear on boards. 30 + $can_appear_on_boards = ($object instanceof ManiphestTask); 31 + 32 + $annotations = array(); 33 + if ($has_projects && $can_appear_on_boards) { 34 + $engine = id(new PhabricatorBoardLayoutEngine()) 35 + ->setViewer($viewer) 36 + ->setBoardPHIDs($project_phids) 37 + ->setObjectPHIDs(array($object->getPHID())) 38 + ->executeLayout(); 39 + 40 + // TDOO: Generalize this UI and move it out of Maniphest. 41 + require_celerity_resource('maniphest-task-summary-css'); 42 + 43 + foreach ($project_phids as $project_phid) { 44 + $handle = $handles[$project_phid]; 45 + 46 + $columns = $engine->getObjectColumns( 47 + $project_phid, 48 + $object->getPHID()); 49 + 50 + $annotation = array(); 51 + foreach ($columns as $column) { 52 + $project_id = $column->getProject()->getID(); 53 + 54 + $column_name = pht('(%s)', $column->getDisplayName()); 55 + $column_link = phutil_tag( 56 + 'a', 57 + array( 58 + 'href' => "/project/board/{$project_id}/", 59 + 'class' => 'maniphest-board-link', 60 + ), 61 + $column_name); 62 + 63 + $annotation[] = $column_link; 64 + } 65 + 66 + if ($annotation) { 67 + $annotations[$project_phid] = array( 68 + ' ', 69 + phutil_implode_html(', ', $annotation), 70 + ); 71 + } 72 + } 73 + 74 + } 75 + 76 + if ($has_projects) { 77 + $list = id(new PHUIHandleTagListView()) 78 + ->setHandles($handles) 79 + ->setAnnotations($annotations) 80 + ->setShowHovercards(true); 81 + } else { 82 + $list = phutil_tag('em', array(), pht('None')); 83 + } 84 + 85 + return $this->newPanel() 86 + ->setHeaderText(pht('Projects')) 87 + ->setOrder(10000) 88 + ->appendChild($list); 89 + } 90 + 91 + }
+39
src/applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php
··· 1 + <?php 2 + 3 + final class PhabricatorSubscriptionsCurtainExtension 4 + extends PHUICurtainExtension { 5 + 6 + const EXTENSIONKEY = 'subscriptions.subscribers'; 7 + 8 + public function shouldEnableForObject($object) { 9 + return ($object instanceof PhabricatorSubscribableInterface); 10 + } 11 + 12 + public function getExtensionApplication() { 13 + return new PhabricatorSubscriptionsApplication(); 14 + } 15 + 16 + public function buildCurtainPanel($object) { 17 + $viewer = $this->getViewer(); 18 + $object_phid = $object->getPHID(); 19 + 20 + $subscriber_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( 21 + $object_phid); 22 + 23 + $handles = $viewer->loadHandles($subscriber_phids); 24 + 25 + // TODO: This class can't accept a HandleList yet. 26 + $handles = iterator_to_array($handles); 27 + 28 + $susbscribers_view = id(new SubscriptionListStringBuilder()) 29 + ->setObjectPHID($object_phid) 30 + ->setHandles($handles) 31 + ->buildPropertyString(); 32 + 33 + return $this->newPanel() 34 + ->setHeaderText(pht('Subscribers')) 35 + ->setOrder(20000) 36 + ->appendChild($susbscribers_view); 37 + } 38 + 39 + }
+67
src/applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php
··· 1 + <?php 2 + 3 + final class PhabricatorTokensCurtainExtension 4 + extends PHUICurtainExtension { 5 + 6 + const EXTENSIONKEY = 'tokens.tokens'; 7 + 8 + public function shouldEnableForObject($object) { 9 + return ($object instanceof PhabricatorTokenReceiverInterface); 10 + } 11 + 12 + public function getExtensionApplication() { 13 + return new PhabricatorTokensApplication(); 14 + } 15 + 16 + public function buildCurtainPanel($object) { 17 + $viewer = $this->getViewer(); 18 + 19 + $tokens_given = id(new PhabricatorTokenGivenQuery()) 20 + ->setViewer($viewer) 21 + ->withObjectPHIDs(array($object->getPHID())) 22 + ->execute(); 23 + if (!$tokens_given) { 24 + return null; 25 + } 26 + 27 + $author_phids = mpull($tokens_given, 'getAuthorPHID'); 28 + $handles = $viewer->loadHandles($author_phids); 29 + 30 + Javelin::initBehavior('phabricator-tooltips'); 31 + 32 + $list = array(); 33 + foreach ($tokens_given as $token_given) { 34 + $token = $token_given->getToken(); 35 + 36 + $aural = javelin_tag( 37 + 'span', 38 + array( 39 + 'aural' => true, 40 + ), 41 + pht( 42 + '"%s" token, awarded by %s.', 43 + $token->getName(), 44 + $handles[$token_given->getAuthorPHID()]->getName())); 45 + 46 + $list[] = javelin_tag( 47 + 'span', 48 + array( 49 + 'sigil' => 'has-tooltip', 50 + 'class' => 'token-icon', 51 + 'meta' => array( 52 + 'tip' => $handles[$token_given->getAuthorPHID()]->getName(), 53 + ), 54 + ), 55 + array( 56 + $aural, 57 + $token->renderIcon(), 58 + )); 59 + } 60 + 61 + return $this->newPanel() 62 + ->setHeaderText(pht('Tokens')) 63 + ->setOrder(30000) 64 + ->appendChild($list); 65 + } 66 + 67 + }
+28 -1
src/applications/tokens/query/PhabricatorTokenGivenQuery.php
··· 58 58 } 59 59 60 60 protected function willFilterPage(array $results) { 61 + $viewer = $this->getViewer(); 62 + 61 63 $object_phids = mpull($results, 'getObjectPHID'); 62 64 63 65 $objects = id(new PhabricatorObjectQuery()) 64 - ->setViewer($this->getViewer()) 66 + ->setViewer($viewer) 65 67 ->withPHIDs($object_phids) 66 68 ->execute(); 67 69 $objects = mpull($objects, null, 'getPHID'); ··· 78 80 79 81 $this->didRejectResult($result); 80 82 unset($results[$key]); 83 + } 84 + 85 + if (!$results) { 86 + return $results; 87 + } 88 + 89 + $token_phids = mpull($results, 'getTokenPHID'); 90 + 91 + $tokens = id(new PhabricatorTokenQuery()) 92 + ->setViewer($viewer) 93 + ->withPHIDs($token_phids) 94 + ->execute(); 95 + $tokens = mpull($tokens, null, 'getPHID'); 96 + 97 + foreach ($results as $key => $result) { 98 + $token_phid = $result->getTokenPHID(); 99 + 100 + $token = idx($tokens, $token_phid); 101 + if (!$token) { 102 + $this->didRejectResult($result); 103 + unset($results[$key]); 104 + continue; 105 + } 106 + 107 + $result->attachToken($token); 81 108 } 82 109 83 110 return $results;
+10
src/applications/tokens/storage/PhabricatorTokenGiven.php
··· 8 8 protected $tokenPHID; 9 9 10 10 private $object = self::ATTACHABLE; 11 + private $token = self::ATTACHABLE; 11 12 12 13 protected function getConfiguration() { 13 14 return array( ··· 33 34 34 35 public function getObject() { 35 36 return $this->assertAttached($this->object); 37 + } 38 + 39 + public function attachToken(PhabricatorToken $token) { 40 + $this->token = $token; 41 + return $this; 42 + } 43 + 44 + public function getToken() { 45 + return $this->assertAttached($this->token); 36 46 } 37 47 38 48 public function getCapabilities() {
+124
src/view/extension/PHUICurtainExtension.php
··· 1 + <?php 2 + 3 + abstract class PHUICurtainExtension extends Phobject { 4 + 5 + private $viewer; 6 + 7 + public function setViewer(PhabricatorUser $viewer) { 8 + $this->viewer = $viewer; 9 + return $this; 10 + } 11 + 12 + public function getViewer() { 13 + return $this->viewer; 14 + } 15 + 16 + abstract public function shouldEnableForObject($object); 17 + abstract public function getExtensionApplication(); 18 + 19 + public function buildCurtainPanels($object) { 20 + $panel = $this->buildCurtainPanel($object); 21 + 22 + if ($panel !== null) { 23 + return array($panel); 24 + } 25 + 26 + return array(); 27 + } 28 + 29 + public function buildCurtainPanel($object) { 30 + throw new PhutilMethodNotImplementedException(); 31 + } 32 + 33 + final public function getExtensionKey() { 34 + return $this->getPhobjectClassConstant('EXTENSIONKEY'); 35 + } 36 + 37 + final public static function getAllExtensions() { 38 + return id(new PhutilClassMapQuery()) 39 + ->setAncestorClass(__CLASS__) 40 + ->setUniqueMethod('getExtensionKey') 41 + ->execute(); 42 + } 43 + 44 + protected function newPanel() { 45 + return new PHUICurtainPanelView(); 46 + } 47 + 48 + final public static function buildExtensionPanels( 49 + PhabricatorUser $viewer, 50 + $object) { 51 + 52 + $extensions = self::getAllExtensions(); 53 + foreach ($extensions as $extension) { 54 + $extension->setViewer($viewer); 55 + } 56 + 57 + foreach ($extensions as $key => $extension) { 58 + $application = $extension->getExtensionApplication(); 59 + if (!($application instanceof PhabricatorApplication)) { 60 + throw new Exception( 61 + pht( 62 + 'Curtain extension ("%s", of class "%s") did not return an '. 63 + 'application from method "%s". This method must return an '. 64 + 'object of class "%s".', 65 + $key, 66 + get_class($extension), 67 + 'getExtensionApplication()', 68 + 'PhabricatorApplication')); 69 + } 70 + 71 + $has_application = PhabricatorApplication::isClassInstalledForViewer( 72 + get_class($application), 73 + $viewer); 74 + 75 + if (!$has_application) { 76 + unset($extensions[$key]); 77 + } 78 + } 79 + 80 + foreach ($extensions as $key => $extension) { 81 + if (!$extension->shouldEnableForObject($object)) { 82 + unset($extensions[$key]); 83 + } 84 + } 85 + 86 + $result = array(); 87 + 88 + foreach ($extensions as $key => $extension) { 89 + $panels = $extension->buildCurtainPanels($object); 90 + if (!is_array($panels)) { 91 + throw new Exception( 92 + pht( 93 + 'Curtain extension ("%s", of class "%s") did not return a list of '. 94 + 'curtain panels from method "%s". This method must return an '. 95 + 'array, and each value in the array must be a "%s" object.', 96 + $key, 97 + get_class($extension), 98 + 'buildCurtainPanels()', 99 + 'PHUICurtainPanelView')); 100 + } 101 + 102 + foreach ($panels as $panel_key => $panel) { 103 + if (!($panel instanceof PHUICurtainPanelView)) { 104 + throw new Exception( 105 + pht( 106 + 'Curtain extension ("%s", of class "%s") returned a list of '. 107 + 'curtain panels from "%s" that contains an invalid value: '. 108 + 'a value (with key "%s") is not an object of class "%s". '. 109 + 'Each item in the returned array must be a panel.', 110 + $key, 111 + get_class($extension), 112 + 'buildCurtainPanels()', 113 + $panel_key, 114 + 'PHUICurtainPanelView')); 115 + } 116 + 117 + $result[] = $panel; 118 + } 119 + } 120 + 121 + return $result; 122 + } 123 + 124 + }
+63
src/view/layout/PHUICurtainPanelView.php
··· 1 + <?php 2 + 3 + final class PHUICurtainPanelView extends AphrontTagView { 4 + 5 + private $order = 0; 6 + private $headerText; 7 + 8 + public function setHeaderText($header_text) { 9 + $this->headerText = $header_text; 10 + return $this; 11 + } 12 + 13 + public function getHeaderText() { 14 + return $this->headerText; 15 + } 16 + 17 + public function setOrder($order) { 18 + $this->order = $order; 19 + return $this; 20 + } 21 + 22 + public function getOrder() { 23 + return $this->order; 24 + } 25 + 26 + public function getOrderVector() { 27 + return id(new PhutilSortVector()) 28 + ->addInt($this->getOrder()); 29 + } 30 + 31 + protected function getTagAttributes() { 32 + return array( 33 + 'class' => 'phui-curtain-panel', 34 + ); 35 + } 36 + 37 + protected function getTagContent() { 38 + $header = null; 39 + 40 + $header_text = $this->getHeaderText(); 41 + if (strlen($header_text)) { 42 + $header = phutil_tag( 43 + 'div', 44 + array( 45 + 'class' => 'phui-curtain-panel-header', 46 + ), 47 + $header_text); 48 + } 49 + 50 + $body = phutil_tag( 51 + 'div', 52 + array( 53 + 'class' => 'phui-curtain-panel-body', 54 + ), 55 + $this->renderChildren()); 56 + 57 + return array( 58 + $header, 59 + $body, 60 + ); 61 + } 62 + 63 + }
+52
src/view/layout/PHUICurtainView.php
··· 1 + <?php 2 + 3 + final class PHUICurtainView extends AphrontTagView { 4 + 5 + private $actionList; 6 + private $panels = array(); 7 + 8 + public function addAction(PhabricatorActionView $action) { 9 + $this->getActionList()->addAction($action); 10 + return $this; 11 + } 12 + 13 + public function addPanel(PHUICurtainPanelView $curtain_panel) { 14 + $this->panels[] = $curtain_panel; 15 + return $this; 16 + } 17 + 18 + public function setActionList(PhabricatorActionListView $action_list) { 19 + $this->actionList = $action_list; 20 + return $this; 21 + } 22 + 23 + public function getActionList() { 24 + return $this->actionList; 25 + } 26 + 27 + protected function canAppendChild() { 28 + return false; 29 + } 30 + 31 + protected function getTagContent() { 32 + $action_list = $this->actionList; 33 + 34 + require_celerity_resource('phui-curtain-view-css'); 35 + 36 + $panels = $this->renderPanels(); 37 + 38 + return id(new PHUIObjectBoxView()) 39 + ->appendChild($action_list) 40 + ->appendChild($panels) 41 + ->addClass('phui-two-column-properties'); 42 + } 43 + 44 + private function renderPanels() { 45 + $panels = $this->panels; 46 + $panels = msortv($panels, 'getOrderVector'); 47 + 48 + return $panels; 49 + } 50 + 51 + 52 + }
+23 -2
src/view/phui/PHUITwoColumnView.php
··· 11 11 private $propertySection = array(); 12 12 private $actionList; 13 13 private $propertyList; 14 + private $curtain; 14 15 15 16 const DISPLAY_LEFT = 'phui-side-column-left'; 16 17 const DISPLAY_RIGHT = 'phui-side-column-right'; ··· 48 49 public function setPropertyList(PHUIPropertyListView $list) { 49 50 $this->propertyList = $list; 50 51 return $this; 52 + } 53 + 54 + public function setCurtain(PHUICurtainView $curtain) { 55 + $this->curtain = $curtain; 56 + return $this; 57 + } 58 + 59 + public function getCurtain() { 60 + return $this->curtain; 51 61 } 52 62 53 63 public function setFluid($fluid) { ··· 98 108 99 109 $header = null; 100 110 if ($this->header) { 101 - if ($this->actionList) { 102 - $this->header->setActionList($this->actionList); 111 + $curtain = $this->getCurtain(); 112 + if ($curtain) { 113 + $action_list = $curtain->getActionList(); 114 + } else { 115 + $action_list = $this->actionList; 116 + } 117 + 118 + if ($action_list) { 119 + $this->header->setActionList($action_list); 103 120 } 121 + 104 122 $header = phutil_tag_div( 105 123 'phui-two-column-header', $this->header); 106 124 } ··· 166 184 ->addClass('phui-two-column-properties'); 167 185 } 168 186 187 + $curtain = $this->getCurtain(); 188 + 169 189 return phutil_tag( 170 190 'div', 171 191 array( ··· 173 193 ), 174 194 array( 175 195 $properties, 196 + $curtain, 176 197 $this->sideColumn, 177 198 )); 178 199 }
+22
webroot/rsrc/css/phui/phui-curtain-view.css
··· 1 + /** 2 + * @provides phui-curtain-view-css 3 + */ 4 + 5 + .phui-curtain-panel { 6 + margin: 4px; 7 + padding: 4px 0; 8 + } 9 + 10 + .device-desktop .phui-curtain-panel { 11 + border-top: 1px solid {$lightblueborder}; 12 + } 13 + 14 + .phui-curtain-panel-header { 15 + padding: 4px 0 0; 16 + color: {$bluetext}; 17 + font-weight: bold; 18 + } 19 + 20 + .phui-curtain-panel-body { 21 + padding: 4px 0 0; 22 + }