diff --git a/src/applications/pholio/controller/PholioMockViewController.php b/src/applications/pholio/controller/PholioMockViewController.php index 565d2d6cb4..906454c1f3 100644 --- a/src/applications/pholio/controller/PholioMockViewController.php +++ b/src/applications/pholio/controller/PholioMockViewController.php @@ -1,236 +1,237 @@ maniphestTaskPHIDs = $maniphest_task_phids; return $this; } private function getManiphestTaskPHIDs() { return $this->maniphestTaskPHIDs; } public function shouldAllowPublic() { return true; } public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); $image_id = $request->getURIData('imageID'); $mock = id(new PholioMockQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needImages(true) ->needInlineComments(true) ->executeOne(); if (!$mock) { return new Aphront404Response(); } $phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $mock->getPHID(), PholioMockHasTaskEdgeType::EDGECONST); $this->setManiphestTaskPHIDs($phids); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($viewer); $engine->addObject($mock, PholioMock::MARKUP_FIELD_DESCRIPTION); $title = $mock->getName(); if ($mock->isClosed()) { $header_icon = 'fa-ban'; $header_name = pht('Closed'); $header_color = 'dark'; } else { $header_icon = 'fa-square-o'; $header_name = pht('Open'); $header_color = 'bluegrey'; } $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) ->setStatus($header_icon, $header_color, $header_name) ->setPolicyObject($mock) ->setHeaderIcon('fa-camera-retro'); $timeline = $this->buildTransactionTimeline( $mock, new PholioTransactionQuery(), $engine); $timeline->setMock($mock); $curtain = $this->buildCurtainView($mock); $details = $this->buildDescriptionView($mock, $engine); require_celerity_resource('pholio-css'); require_celerity_resource('pholio-inline-comments-css'); $comment_form_id = celerity_generate_unique_node_id(); $mock_view = id(new PholioMockImagesView()) ->setRequestURI($request->getRequestURI()) ->setCommentFormID($comment_form_id) ->setUser($viewer) ->setMock($mock) ->setImageID($image_id); $output = id(new PHUIObjectBoxView()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($mock_view); $add_comment = $this->buildAddCommentView($mock, $comment_form_id); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb('M'.$mock->getID(), '/M'.$mock->getID()); $crumbs->setBorder(true); $thumb_grid = id(new PholioMockThumbGridView()) ->setUser($viewer) ->setMock($mock); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->setMainColumn(array( $output, $thumb_grid, $details, $timeline, $add_comment, )); return $this->newPage() ->setTitle('M'.$mock->getID().' '.$title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($mock->getPHID())) ->addQuicksandConfig( array('mockViewConfig' => $mock_view->getBehaviorConfig())) ->appendChild($view); } private function buildCurtainView(PholioMock $mock) { $viewer = $this->getViewer(); $curtain = $this->newCurtainView($mock); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $mock, PhabricatorPolicyCapability::CAN_EDIT); $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Mock')) ->setHref($this->getApplicationURI('/edit/'.$mock->getID().'/')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); if ($mock->isClosed()) { $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-check') ->setName(pht('Open Mock')) ->setHref($this->getApplicationURI('/archive/'.$mock->getID().'/')) ->setDisabled(!$can_edit) ->setWorkflow(true)); } else { $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-ban') ->setName(pht('Close Mock')) ->setHref($this->getApplicationURI('/archive/'.$mock->getID().'/')) ->setDisabled(!$can_edit) ->setWorkflow(true)); } $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-anchor') ->setName(pht('Edit Maniphest Tasks')) ->setHref("/search/attach/{$mock->getPHID()}/TASK/edge/") ->setDisabled(!$viewer->isLoggedIn()) ->setWorkflow(true)); if ($this->getManiphestTaskPHIDs()) { $curtain->newPanel() ->setHeaderText(pht('Maniphest Tasks')) ->appendChild( $viewer->renderHandleList($this->getManiphestTaskPHIDs())); } $curtain->newPanel() ->setHeaderText(pht('Authored By')) ->appendChild($this->buildAuthorPanel($mock)); return $curtain; } private function buildDescriptionView(PholioMock $mock) { - $viewer = $this->getViewer(); + $properties = id(new PHUIPropertyListView()) ->setUser($viewer); $description = $mock->getDescription(); if (strlen($description)) { - $properties->addImageContent($description); + $properties->addTextContent( + new PHUIRemarkupView($viewer, $description)); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Mock Description')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($properties); } return null; } private function buildAuthorPanel(PholioMock $mock) { $viewer = $this->getViewer(); $author_phid = $mock->getAuthorPHID(); $handles = $viewer->loadHandles(array($author_phid)); $author_uri = $handles[$author_phid]->getImageURI(); $author_href = $handles[$author_phid]->getURI(); $author = $viewer->renderHandle($author_phid)->render(); $content = phutil_tag('strong', array(), $author); $date = phabricator_date($mock->getDateCreated(), $viewer); $content = pht('%s, %s', $content, $date); $authored_by = id(new PHUIHeadThingView()) ->setImage($author_uri) ->setImageHref($author_href) ->setContent($content); return $authored_by; } private function buildAddCommentView(PholioMock $mock, $comment_form_id) { $viewer = $this->getViewer(); $draft = PhabricatorDraft::newFromUserAndKey($viewer, $mock->getPHID()); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $title = $is_serious ? pht('Add Comment') : pht('History Beckons'); $form = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setObjectPHID($mock->getPHID()) ->setFormID($comment_form_id) ->setDraft($draft) ->setHeaderText($title) ->setSubmitButtonName(pht('Add Comment')) ->setAction($this->getApplicationURI('/comment/'.$mock->getID().'/')) ->setRequestURI($this->getRequest()->getRequestURI()); return $form; } } diff --git a/src/applications/pholio/view/PholioMockImagesView.php b/src/applications/pholio/view/PholioMockImagesView.php index d50dfb3b3a..6c92f1d21f 100644 --- a/src/applications/pholio/view/PholioMockImagesView.php +++ b/src/applications/pholio/view/PholioMockImagesView.php @@ -1,228 +1,238 @@ commentFormID = $comment_form_id; return $this; } public function getCommentFormID() { return $this->commentFormID; } public function setRequestURI(PhutilURI $request_uri) { $this->requestURI = $request_uri; return $this; } public function getRequestURI() { return $this->requestURI; } public function setImageID($image_id) { $this->imageID = $image_id; return $this; } public function getImageID() { return $this->imageID; } public function setMock(PholioMock $mock) { $this->mock = $mock; return $this; } public function getMock() { return $this->mock; } public function __construct() { $this->panelID = celerity_generate_unique_node_id(); $this->viewportID = celerity_generate_unique_node_id(); } public function getBehaviorConfig() { if (!$this->getMock()) { throw new PhutilInvalidStateException('setMock'); } if ($this->behaviorConfig === null) { $this->behaviorConfig = $this->calculateBehaviorConfig(); } return $this->behaviorConfig; } private function calculateBehaviorConfig() { $mock = $this->getMock(); // TODO: We could maybe do a better job with tailoring this, which is the // image shown on the review stage. $viewer = $this->getUser(); $default = PhabricatorFile::loadBuiltin($viewer, 'image-100x100.png'); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($this->getUser()); foreach ($mock->getAllImages() as $image) { $engine->addObject($image, 'default'); } $engine->process(); $images = array(); $current_set = 0; foreach ($mock->getAllImages() as $image) { $file = $image->getFile(); $metadata = $file->getMetadata(); $x = idx($metadata, PhabricatorFile::METADATA_IMAGE_WIDTH); $y = idx($metadata, PhabricatorFile::METADATA_IMAGE_HEIGHT); $is_obs = (bool)$image->getIsObsolete(); if (!$is_obs) { $current_set++; } + $description = $engine->getOutput($image, 'default'); + if (strlen($description)) { + $description = phutil_tag( + 'div', + array( + 'class' => 'phabricator-remarkup', + ), + $description); + } + $history_uri = '/pholio/image/history/'.$image->getID().'/'; $images[] = array( 'id' => $image->getID(), 'fullURI' => $file->getBestURI(), 'stageURI' => ($file->isViewableImage() ? $file->getBestURI() : $default->getBestURI()), 'pageURI' => $this->getImagePageURI($image, $mock), 'downloadURI' => $file->getDownloadURI(), 'historyURI' => $history_uri, 'width' => $x, 'height' => $y, 'title' => $image->getName(), - 'descriptionMarkup' => $engine->getOutput($image, 'default'), + 'descriptionMarkup' => $description, 'isObsolete' => (bool)$image->getIsObsolete(), 'isImage' => $file->isViewableImage(), 'isViewable' => $file->isViewableInBrowser(), ); } $ids = mpull($mock->getImages(), 'getID'); if ($this->imageID && isset($ids[$this->imageID])) { $selected_id = $this->imageID; } else { $selected_id = head_key($ids); } $navsequence = array(); foreach ($mock->getImages() as $image) { $navsequence[] = $image->getID(); } $full_icon = array( javelin_tag('span', array('aural' => true), pht('View Raw File')), id(new PHUIIconView())->setIcon('fa-file-image-o'), ); $download_icon = array( javelin_tag('span', array('aural' => true), pht('Download File')), id(new PHUIIconView())->setIcon('fa-download'), ); $login_uri = id(new PhutilURI('/login/')) ->setQueryParam('next', (string)$this->getRequestURI()); $config = array( 'mockID' => $mock->getID(), 'panelID' => $this->panelID, 'viewportID' => $this->viewportID, 'commentFormID' => $this->getCommentFormID(), 'images' => $images, 'selectedID' => $selected_id, 'loggedIn' => $this->getUser()->isLoggedIn(), 'logInLink' => (string)$login_uri, 'navsequence' => $navsequence, 'fullIcon' => hsprintf('%s', $full_icon), 'downloadIcon' => hsprintf('%s', $download_icon), 'currentSetSize' => $current_set, ); return $config; } public function render() { if (!$this->getMock()) { throw new PhutilInvalidStateException('setMock'); } $mock = $this->getMock(); require_celerity_resource('javelin-behavior-pholio-mock-view'); $panel_id = $this->panelID; $viewport_id = $this->viewportID; $config = $this->getBehaviorConfig(); Javelin::initBehavior( 'pholio-mock-view', $this->getBehaviorConfig()); $mockview = ''; $mock_wrapper = javelin_tag( 'div', array( 'id' => $this->viewportID, 'sigil' => 'mock-viewport', 'class' => 'pholio-mock-image-viewport', ), ''); $image_header = javelin_tag( 'div', array( 'id' => 'mock-image-header', 'class' => 'pholio-mock-image-header', ), ''); $mock_wrapper = javelin_tag( 'div', array( 'id' => $this->panelID, 'sigil' => 'mock-panel touchable', 'class' => 'pholio-mock-image-panel', ), array( $image_header, $mock_wrapper, )); $inline_comments_holder = javelin_tag( 'div', array( 'id' => 'mock-image-description', 'sigil' => 'mock-image-description', 'class' => 'mock-image-description', ), ''); $mockview[] = phutil_tag( 'div', array( 'class' => 'pholio-mock-image-container', 'id' => 'pholio-mock-image-container', ), array($mock_wrapper, $inline_comments_holder)); return $mockview; } private function getImagePageURI(PholioImage $image, PholioMock $mock) { $uri = '/M'.$mock->getID().'/'.$image->getID().'/'; return $uri; } }