diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index da9f35c6e..f1f6bcff7 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -1,176 +1,171 @@ getViewer(); $id = $request->getURIData('id'); $rule = id(new HeraldRuleQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needConditionsAndActions(true) ->executeOne(); if (!$rule) { return new Aphront404Response(); } $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($rule->getName()) ->setPolicyObject($rule); if ($rule->getIsDisabled()) { $header->setStatus( 'fa-ban', 'red', pht('Archived')); } else { $header->setStatus( 'fa-check', 'bluegrey', pht('Active')); } $actions = $this->buildActionView($rule); $properties = $this->buildPropertyView($rule); - $details = $this->buildDetailsView($rule); + $details = $this->buildPropertySectionView($rule); $id = $rule->getID(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb("H{$id}"); $crumbs->setBorder(true); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); $timeline = $this->buildTransactionTimeline( $rule, new HeraldTransactionQuery()); $timeline->setShouldTerminate(true); $title = $rule->getName(); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setMainColumn(array( - $details, - $timeline, - )) + ->setMainColumn($timeline) + ->addPropertySection(pht('DETAILS'), $details) ->setPropertyList($properties) ->setActionList($actions); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( $view, )); } private function buildActionView(HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); $id = $rule->getID(); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($rule); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $rule, PhabricatorPolicyCapability::CAN_EDIT); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Rule')) ->setHref($this->getApplicationURI("edit/{$id}/")) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); if ($rule->getIsDisabled()) { $disable_uri = "disable/{$id}/enable/"; $disable_icon = 'fa-check'; $disable_name = pht('Activate Rule'); } else { $disable_uri = "disable/{$id}/disable/"; $disable_icon = 'fa-ban'; $disable_name = pht('Archive Rule'); } $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Disable Rule')) ->setHref($this->getApplicationURI($disable_uri)) ->setIcon($disable_icon) ->setName($disable_name) ->setDisabled(!$can_edit) ->setWorkflow(true)); return $view; } private function buildPropertyView( HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($rule); $view->invokeWillRenderEvent(); return $view; } - private function buildDetailsView( + private function buildPropertySectionView( HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer); $view->addProperty( pht('Rule Type'), idx(HeraldRuleTypeConfig::getRuleTypeMap(), $rule->getRuleType())); if ($rule->isPersonalRule()) { $view->addProperty( pht('Author'), $viewer->renderHandle($rule->getAuthorPHID())); } $adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType()); if ($adapter) { $view->addProperty( pht('Applies To'), idx( HeraldAdapter::getEnabledAdapterMap($viewer), $rule->getContentType())); if ($rule->isObjectRule()) { $view->addProperty( pht('Trigger Object'), $viewer->renderHandle($rule->getTriggerObjectPHID())); } $view->addSectionHeader( pht('Rule Description'), PHUIPropertyListView::ICON_SUMMARY); $handles = $viewer->loadHandles(HeraldAdapter::getHandlePHIDs($rule)); $rule_text = $adapter->renderRuleAsText($rule, $handles, $viewer); $view->addTextContent($rule_text); } - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DETAILS')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($view); + return $view; } } diff --git a/src/applications/passphrase/controller/PassphraseCredentialViewController.php b/src/applications/passphrase/controller/PassphraseCredentialViewController.php index b4ccf0ad9..5ec2fe7c0 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php @@ -1,253 +1,251 @@ getViewer(); $id = $request->getURIData('id'); $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$credential) { return new Aphront404Response(); } $type = PassphraseCredentialType::getTypeByConstant( $credential->getCredentialType()); if (!$type) { throw new Exception(pht('Credential has invalid type "%s"!', $type)); } $timeline = $this->buildTransactionTimeline( $credential, new PassphraseCredentialTransactionQuery()); $timeline->setShouldTerminate(true); $title = pht('%s %s', 'K'.$credential->getID(), $credential->getName()); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb('K'.$credential->getID()); $crumbs->setBorder(true); $header = $this->buildHeaderView($credential); $actions = $this->buildActionView($credential, $type); $properties = $this->buildPropertyView($credential, $type); $subheader = $this->buildSubheaderView($credential); - $content = $this->buildDetailsView($credential, $type); + $content = $this->buildPropertySectionView($credential, $type); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) - ->setMainColumn(array($content, $timeline)) + ->setMainColumn($timeline) + ->addPropertySection(pht('PROPERTIES'), $content) ->setPropertyList($properties) ->setActionList($actions); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( $view, )); } private function buildHeaderView(PassphraseCredential $credential) { $viewer = $this->getRequest()->getUser(); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($credential->getName()) ->setPolicyObject($credential); if ($credential->getIsDestroyed()) { $header->setStatus('fa-ban', 'red', pht('Destroyed')); } return $header; } private function buildSubheaderView( PassphraseCredential $credential) { $viewer = $this->getViewer(); $author = $viewer->renderHandle($credential->getAuthorPHID())->render(); $date = phabricator_datetime($credential->getDateCreated(), $viewer); $author = phutil_tag('strong', array(), $author); $person = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) ->withPHIDs(array($credential->getAuthorPHID())) ->needProfileImage(true) ->executeOne(); if (!$person) { return null; } $image_uri = $person->getProfileImageURI(); $image_href = '/p/'.$credential->getUsername(); $content = pht('Created by %s on %s.', $author, $date); return id(new PHUIHeadThingView()) ->setImage($image_uri) ->setImageHref($image_href) ->setContent($content); } private function buildActionView( PassphraseCredential $credential, PassphraseCredentialType $type) { $viewer = $this->getRequest()->getUser(); $id = $credential->getID(); $is_locked = $credential->getIsLocked(); if ($is_locked) { $credential_lock_text = pht('Locked Permanently'); $credential_lock_icon = 'fa-lock'; } else { $credential_lock_text = pht('Lock Permanently'); $credential_lock_icon = 'fa-unlock'; } $allow_conduit = $credential->getAllowConduit(); if ($allow_conduit) { $credential_conduit_text = pht('Prevent Conduit Access'); $credential_conduit_icon = 'fa-ban'; } else { $credential_conduit_text = pht('Allow Conduit Access'); $credential_conduit_icon = 'fa-wrench'; } $actions = id(new PhabricatorActionListView()) ->setObject($credential) ->setUser($viewer); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $credential, PhabricatorPolicyCapability::CAN_EDIT); $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Credential')) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI("edit/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); if (!$credential->getIsDestroyed()) { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Destroy Credential')) ->setIcon('fa-times') ->setHref($this->getApplicationURI("destroy/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(true)); $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Secret')) ->setIcon('fa-eye') ->setHref($this->getApplicationURI("reveal/{$id}/")) ->setDisabled(!$can_edit || $is_locked) ->setWorkflow(true)); if ($type->hasPublicKey()) { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Public Key')) ->setIcon('fa-download') ->setHref($this->getApplicationURI("public/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(true)); } $actions->addAction( id(new PhabricatorActionView()) ->setName($credential_conduit_text) ->setIcon($credential_conduit_icon) ->setHref($this->getApplicationURI("conduit/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(true)); $actions->addAction( id(new PhabricatorActionView()) ->setName($credential_lock_text) ->setIcon($credential_lock_icon) ->setHref($this->getApplicationURI("lock/{$id}/")) ->setDisabled(!$can_edit || $is_locked) ->setWorkflow(true)); } return $actions; } - private function buildDetailsView( + private function buildPropertySectionView( PassphraseCredential $credential, PassphraseCredentialType $type) { $viewer = $this->getRequest()->getUser(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer); $properties->addProperty( pht('Credential Type'), $type->getCredentialTypeName()); $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, $credential); $properties->addProperty( pht('Editable By'), $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); if ($type->shouldRequireUsername()) { $properties->addProperty( pht('Username'), $credential->getUsername()); } $used_by_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $credential->getPHID(), PhabricatorCredentialsUsedByObjectEdgeType::EDGECONST); if ($used_by_phids) { $properties->addProperty( pht('Used By'), $viewer->renderHandleList($used_by_phids)); } $description = $credential->getDescription(); if (strlen($description)) { $properties->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $properties->addTextContent( new PHUIRemarkupView($viewer, $description)); } - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('PROPERTIES')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($properties); + return $properties; } private function buildPropertyView( PassphraseCredential $credential, PassphraseCredentialType $type) { $viewer = $this->getRequest()->getUser(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($credential); $properties->invokeWillRenderEvent(); return $properties; } } diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 75a458fdc..3a1a21007 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -1,301 +1,296 @@ getViewer(); $id = $request->getURIData('id'); $question = id(new PonderQuestionQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needAnswers(true) ->needProjectPHIDs(true) ->executeOne(); if (!$question) { return new Aphront404Response(); } $answers = $this->buildAnswers($question); $answer_add_panel = id(new PonderAddAnswerView()) ->setQuestion($question) ->setUser($viewer) ->setActionURI('/ponder/answer/add/'); $header = new PHUIHeaderView(); $header->setHeader($question->getTitle()); $header->setUser($viewer); $header->setPolicyObject($question); if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { $header->setStatus('fa-square-o', 'bluegrey', pht('Open')); } else { $text = PonderQuestionStatus::getQuestionStatusFullName( $question->getStatus()); $icon = PonderQuestionStatus::getQuestionStatusIcon( $question->getStatus()); $header->setStatus($icon, 'dark', $text); } $properties = $this->buildPropertyListView($question); $actions = $this->buildActionListView($question); - $details = $this->buildDetailsPropertyView($question); + $details = $this->buildPropertySectionView($question); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $question, PhabricatorPolicyCapability::CAN_EDIT); $content_id = celerity_generate_unique_node_id(); $timeline = $this->buildTransactionTimeline( $question, id(new PonderQuestionTransactionQuery()) ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))); $xactions = $timeline->getTransactions(); $add_comment = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setObjectPHID($question->getPHID()) ->setShowPreview(false) ->setAction($this->getApplicationURI("/question/comment/{$id}/")) ->setSubmitButtonName(pht('Comment')); $add_comment = phutil_tag_div( 'ponder-question-add-comment-view', $add_comment); $comment_view = phutil_tag( 'div', array( 'id' => $content_id, 'style' => 'display: none;', ), array( $timeline, $add_comment, )); $footer = id(new PonderFooterView()) ->setContentID($content_id) ->setCount(count($xactions)); $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); $crumbs->addTextCrumb('Q'.$id, '/Q'.$id); $crumbs->setBorder(true); $subheader = $this->buildSubheaderView($question); $answer_wiki = null; if ($question->getAnswerWiki()) { $wiki = new PHUIRemarkupView($viewer, $question->getAnswerWiki()); $answer_wiki = id(new PHUIObjectBoxView()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setHeaderText(pht('ANSWER SUMMARY')) ->appendChild($wiki) ->addClass('ponder-answer-wiki'); } require_celerity_resource('ponder-view-css'); $ponder_content = phutil_tag( 'div', array( 'class' => 'ponder-question-content', ), array( - $details, $footer, $comment_view, $answer_wiki, $answers, $answer_add_panel, )); $ponder_view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) ->setMainColumn($ponder_content) ->setPropertyList($properties) + ->addPropertySection(pht('DETAILS'), $details) ->setActionList($actions) ->addClass('ponder-question-view'); $page_objects = array_merge( array($question->getPHID()), mpull($question->getAnswers(), 'getPHID')); return $this->newPage() ->setTitle('Q'.$question->getID().' '.$question->getTitle()) ->setCrumbs($crumbs) ->setPageObjectPHIDs($page_objects) ->appendChild( array( $ponder_view, )); } private function buildActionListView(PonderQuestion $question) { $viewer = $this->getViewer(); $request = $this->getRequest(); $id = $question->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $question, PhabricatorPolicyCapability::CAN_EDIT); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($question); if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { $name = pht('Close Question'); $icon = 'fa-check-square-o'; } else { $name = pht('Reopen Question'); $icon = 'fa-square-o'; } $view->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Question')) ->setHref($this->getApplicationURI("/question/edit/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $view->addAction( id(new PhabricatorActionView()) ->setName($name) ->setIcon($icon) ->setWorkflow(true) ->setDisabled(!$can_edit) ->setHref($this->getApplicationURI("/question/status/{$id}/"))); $view->addAction( id(new PhabricatorActionView()) ->setIcon('fa-list') ->setName(pht('View History')) ->setHref($this->getApplicationURI("/question/history/{$id}/"))); return $view; } private function buildPropertyListView( PonderQuestion $question) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($question); $view->invokeWillRenderEvent(); return $view; } private function buildSubheaderView( PonderQuestion $question) { $viewer = $this->getViewer(); $asker = $viewer->renderHandle($question->getAuthorPHID())->render(); $date = phabricator_datetime($question->getDateCreated(), $viewer); $asker = phutil_tag('strong', array(), $asker); $author = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) ->withPHIDs(array($question->getAuthorPHID())) ->needProfileImage(true) ->executeOne(); $image_uri = $author->getProfileImageURI(); $image_href = '/p/'.$author->getUsername(); $content = pht('Asked by %s on %s.', $asker, $date); return id(new PHUIHeadThingView()) ->setImage($image_uri) ->setImageHref($image_href) ->setContent($content); } - private function buildDetailsPropertyView( + private function buildPropertySectionView( PonderQuestion $question) { $viewer = $this->getViewer(); $question_details = PhabricatorMarkupEngine::renderOneObject( $question, $question->getMarkupField(), $viewer); if (!$question_details) { $question_details = phutil_tag( 'em', array(), pht('No further details for this question.')); } $question_details = phutil_tag_div( 'phabricator-remarkup ml', $question_details); - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DETAILS')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setFlush(true) - ->appendChild($question_details); + return $question_details; } /** * This is fairly non-standard; building N timelines at once (N = number of * answers) is tricky business. * * TODO - re-factor this to ajax in one answer panel at a time in a more * standard fashion. This is necessary to scale this application. */ private function buildAnswers(PonderQuestion $question) { $viewer = $this->getViewer(); $answers = $question->getAnswers(); if ($answers) { $author_phids = mpull($answers, 'getAuthorPHID'); $handles = $this->loadViewerHandles($author_phids); $view = array(); foreach ($answers as $answer) { $id = $answer->getID(); $handle = $handles[$answer->getAuthorPHID()]; $timeline = $this->buildTransactionTimeline( $answer, id(new PonderAnswerTransactionQuery()) ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))); $xactions = $timeline->getTransactions(); - $view[] = id(new PonderAnswerView()) ->setUser($viewer) ->setAnswer($answer) ->setTransactions($xactions) ->setTimeline($timeline) ->setHandle($handle); } $header = id(new PHUIHeaderView()) ->setHeader('Answers'); return id(new PHUIBoxView()) ->addClass('ponder-answer-section') ->appendChild($header) ->appendChild($view); } return null; } } diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index 62db9c16e..666935c66 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -1,152 +1,177 @@ mainColumn = $main; return $this; } public function setSideColumn($side) { $this->sideColumn = $side; return $this; } public function setHeader(PHUIHeaderView $header) { $this->header = $header; return $this; } public function setSubheader($subheader) { $this->subheader = $subheader; return $this; } + public function addPropertySection($title, $section) { + $this->propertySection[] = array($title, $section); + return $this; + } + public function setActionList(PhabricatorActionListView $list) { $this->actionList = $list; return $this; } public function setPropertyList(PHUIPropertyListView $list) { $this->propertyList = $list; return $this; } public function setFluid($fluid) { $this->fluid = $fluid; return $this; } public function setDisplay($display) { $this->display = $display; return $this; } private function getDisplay() { if ($this->display) { return $this->display; } else { return self::DISPLAY_RIGHT; } } protected function getTagAttributes() { $classes = array(); $classes[] = 'phui-two-column-view'; $classes[] = $this->getDisplay(); if ($this->fluid) { $classes[] = 'phui-two-column-fluid'; } if ($this->subheader) { $classes[] = 'with-subheader'; } return array( 'class' => implode(' ', $classes), ); } protected function getTagContent() { require_celerity_resource('phui-two-column-view-css'); - $main = phutil_tag( - 'div', - array( - 'class' => 'phui-main-column', - ), - $this->mainColumn); - + $main = $this->buildMainColumn(); $side = $this->buildSideColumn(); $order = array($side, $main); $inner = phutil_tag_div('phui-two-column-row grouped', $order); $table = phutil_tag_div('phui-two-column-content', $inner); $header = null; if ($this->header) { if ($this->actionList) { $this->header->setActionList($this->actionList); } $header = phutil_tag_div( 'phui-two-column-header', $this->header); } $subheader = null; if ($this->subheader) { $subheader = phutil_tag_div( 'phui-two-column-subheader', $this->subheader); } return phutil_tag( 'div', array( 'class' => 'phui-two-column-container', ), array( $header, $subheader, $table, )); } + private function buildMainColumn() { + + $view = array(); + $sections = $this->propertySection; + + if ($sections) { + foreach ($sections as $content) { + $view[] = id(new PHUIObjectBoxView()) + ->setHeaderText($content[0]) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($content[1]); + } + } + + return phutil_tag( + 'div', + array( + 'class' => 'phui-main-column', + ), + array( + $view, + $this->mainColumn, + )); + } + private function buildSideColumn() { $property_list = $this->propertyList; $action_list = $this->actionList; $properties = null; if ($property_list || $action_list) { if ($property_list) { $property_list->setStacked(true); } $properties = id(new PHUIObjectBoxView()) ->appendChild($action_list) ->appendChild($property_list) ->addClass('phui-two-column-properties'); } return phutil_tag( 'div', array( 'class' => 'phui-side-column', ), array( $properties, $this->sideColumn, )); } }