diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index a2b2b9e034..fb82bbc705 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -1,383 +1,381 @@ preface = $preface; return $this; } public function getPreface() { return $this->preface; } public function setQueryKey($query_key) { $this->queryKey = $query_key; return $this; } protected function getQueryKey() { return $this->queryKey; } public function setNavigation(AphrontSideNavFilterView $navigation) { $this->navigation = $navigation; return $this; } protected function getNavigation() { return $this->navigation; } public function setSearchEngine( PhabricatorApplicationSearchEngine $search_engine) { $this->searchEngine = $search_engine; return $this; } protected function getSearchEngine() { return $this->searchEngine; } protected function validateDelegatingController() { $parent = $this->getDelegatingController(); if (!$parent) { throw new Exception( pht('You must delegate to this controller, not invoke it directly.')); } $engine = $this->getSearchEngine(); if (!$engine) { throw new PhutilInvalidStateException('setEngine'); } $nav = $this->getNavigation(); if (!$nav) { throw new PhutilInvalidStateException('setNavigation'); } $engine->setViewer($this->getRequest()->getUser()); $parent = $this->getDelegatingController(); } public function processRequest() { $this->validateDelegatingController(); $key = $this->getQueryKey(); if ($key == 'edit') { return $this->processEditRequest(); } else { return $this->processSearchRequest(); } } private function processSearchRequest() { $parent = $this->getDelegatingController(); $request = $this->getRequest(); $user = $request->getUser(); $engine = $this->getSearchEngine(); $nav = $this->getNavigation(); if ($request->isFormPost()) { $saved_query = $engine->buildSavedQueryFromRequest($request); $engine->saveQuery($saved_query); return id(new AphrontRedirectResponse())->setURI( $engine->getQueryResultsPageURI($saved_query->getQueryKey()).'#R'); } $named_query = null; $run_query = true; $query_key = $this->queryKey; if ($this->queryKey == 'advanced') { $run_query = false; $query_key = $request->getStr('query'); } else if (!strlen($this->queryKey)) { $found_query_data = false; if ($request->isHTTPGet()) { // If this is a GET request and it has some query data, don't // do anything unless it's only before= or after=. We'll build and // execute a query from it below. This allows external tools to build // URIs like "/query/?users=a,b". $pt_data = $request->getPassthroughRequestData(); foreach ($pt_data as $pt_key => $pt_value) { if ($pt_key != 'before' && $pt_key != 'after') { $found_query_data = true; break; } } } if (!$found_query_data) { // Otherwise, there's no query data so just run the user's default // query for this application. $query_key = head_key($engine->loadEnabledNamedQueries()); } } if ($engine->isBuiltinQuery($query_key)) { $saved_query = $engine->buildSavedQueryFromBuiltin($query_key); $named_query = idx($engine->loadEnabledNamedQueries(), $query_key); } else if ($query_key) { $saved_query = id(new PhabricatorSavedQueryQuery()) ->setViewer($user) ->withQueryKeys(array($query_key)) ->executeOne(); if (!$saved_query) { return new Aphront404Response(); } $named_query = idx($engine->loadEnabledNamedQueries(), $query_key); } else { $saved_query = $engine->buildSavedQueryFromRequest($request); // Save the query to generate a query key, so "Save Custom Query..." and // other features like Maniphest's "Export..." work correctly. $engine->saveQuery($saved_query); } $nav->selectFilter( 'query/'.$saved_query->getQueryKey(), 'query/advanced'); $form = id(new AphrontFormView()) ->setUser($user) ->setAction($request->getPath()); $engine->buildSearchForm($form, $saved_query); $errors = $engine->getErrors(); if ($errors) { $run_query = false; } $submit = id(new AphrontFormSubmitControl()) ->setValue(pht('Execute Query')); if ($run_query && !$named_query && $user->isLoggedIn()) { $submit->addCancelButton( '/search/edit/'.$saved_query->getQueryKey().'/', pht('Save Custom Query...')); } // TODO: A "Create Dashboard Panel" action goes here somewhere once // we sort out T5307. $form->appendChild($submit); if ($this->getPreface()) { $nav->appendChild($this->getPreface()); } if ($named_query) { $title = $named_query->getQueryName(); } else { $title = pht('Advanced Search'); } - $box = new PHUIObjectBoxView(); + $header = id(new PHUIHeaderView()) + ->setHeader($title); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header); if ($run_query || $named_query) { $box->setShowHide( pht('Edit Query'), pht('Hide Query'), $form, $this->getApplicationURI('query/advanced/?query='.$query_key), (!$named_query ? true : false)); } else { $box->setForm($form); } $nav->appendChild($box); if ($run_query) { $box->setAnchor( id(new PhabricatorAnchorView()) ->setAnchorName('R')); try { $query = $engine->buildQueryFromSavedQuery($saved_query); $pager = $engine->newPagerForSavedQuery($saved_query); $pager->readFromRequest($request); $objects = $engine->executeQuery($query, $pager); // TODO: To support Dashboard panels, rendering is moving into // SearchEngines. Move it all the way in and then get rid of this. $interface = 'PhabricatorApplicationSearchResultsControllerInterface'; if ($parent instanceof $interface) { $list = $parent->renderResultsList($objects, $saved_query); } else { $engine->setRequest($request); $list = $engine->renderResults( $objects, $saved_query); } - $header = id(new PHUIHeaderView()) - ->setHeader($title); if ($list->getActions()) { foreach ($list->getActions() as $action) { $header->addActionLink($action); } } - $box->setHeader($header); - if ($list->getObjectList()) { $box->setObjectList($list->getObjectList()); } if ($list->getTable()) { $box->setTable($list->getTable()); } if ($list->getInfoView()) { $box->setInfoView($list->getInfoView()); } if ($list->getContent()) { $box->appendChild($list->getContent()); } if ($list->getCollapsed()) { $box->setCollapsed(true); } if ($pager->willShowPagingControls()) { $pager_box = id(new PHUIBoxView()) ->addPadding(PHUI::PADDING_MEDIUM) ->addMargin(PHUI::MARGIN_LARGE) ->setBorder(true) ->appendChild($pager); $nav->appendChild($pager_box); } } catch (PhabricatorTypeaheadInvalidTokenException $ex) { $errors[] = pht( 'This query specifies an invalid parameter. Review the '. 'query parameters and correct errors.'); } - } else { - $box->setHeaderText($title); } if ($errors) { $box->setFormErrors($errors, pht('Query Errors')); } $crumbs = $parent ->buildApplicationCrumbs() ->addTextCrumb($title); $nav->setCrumbs($crumbs); return $this->buildApplicationPage( $nav, array( 'title' => pht('Query: %s', $title), )); } private function processEditRequest() { $parent = $this->getDelegatingController(); $request = $this->getRequest(); $user = $request->getUser(); $engine = $this->getSearchEngine(); $nav = $this->getNavigation(); $named_queries = $engine->loadAllNamedQueries(); $list_id = celerity_generate_unique_node_id(); $list = new PHUIObjectItemListView(); $list->setUser($user); $list->setID($list_id); Javelin::initBehavior( 'search-reorder-queries', array( 'listID' => $list_id, 'orderURI' => '/search/order/'.get_class($engine).'/', )); foreach ($named_queries as $named_query) { $class = get_class($engine); $key = $named_query->getQueryKey(); $item = id(new PHUIObjectItemView()) ->setHeader($named_query->getQueryName()) ->setHref($engine->getQueryResultsPageURI($key)); if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) { $icon = 'fa-plus'; } else { $icon = 'fa-times'; } $item->addAction( id(new PHUIListItemView()) ->setIcon($icon) ->setHref('/search/delete/'.$key.'/'.$class.'/') ->setWorkflow(true)); if ($named_query->getIsBuiltin()) { if ($named_query->getIsDisabled()) { $item->addIcon('fa-times lightgreytext', pht('Disabled')); $item->setDisabled(true); } else { $item->addIcon('fa-lock lightgreytext', pht('Builtin')); } } else { $item->addAction( id(new PHUIListItemView()) ->setIcon('fa-pencil') ->setHref('/search/edit/'.$key.'/')); } $item->setGrippable(true); $item->addSigil('named-query'); $item->setMetadata( array( 'queryKey' => $named_query->getQueryKey(), )); $list->addItem($item); } $list->setNoDataString(pht('No saved queries.')); $crumbs = $parent ->buildApplicationCrumbs() ->addTextCrumb(pht('Saved Queries'), $engine->getQueryManagementURI()); $nav->selectFilter('query/edit'); $nav->setCrumbs($crumbs); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Saved Queries')) ->setObjectList($list); $nav->appendChild($box); return $parent->buildApplicationPage( $nav, array( 'title' => pht('Saved Queries'), )); } public function buildApplicationMenu() { return $this->getDelegatingController()->buildApplicationMenu(); } } diff --git a/src/view/phui/PHUIObjectBoxView.php b/src/view/phui/PHUIObjectBoxView.php index 9648ab88fe..49c55769b4 100644 --- a/src/view/phui/PHUIObjectBoxView.php +++ b/src/view/phui/PHUIObjectBoxView.php @@ -1,416 +1,420 @@ sigils[] = $sigil; return $this; } public function setMetadata(array $metadata) { $this->metadata = $metadata; return $this; } public function addPropertyList( PHUIPropertyListView $property_list, $tab = null) { if (!($tab instanceof PHUIListItemView) && ($tab !== null)) { assert_stringlike($tab); $tab = id(new PHUIListItemView())->setName($tab); } if ($tab) { if ($tab->getKey()) { $key = $tab->getKey(); } else { $key = 'tab.default.'.spl_object_hash($tab); $tab->setKey($key); } } else { $key = 'tab.default'; } if ($tab) { if (empty($this->tabs[$key])) { $tab->addSigil('phui-object-box-tab'); $tab->setMetadata( array( 'tabKey' => $key, )); if (!$tab->getHref()) { $tab->setHref('#'); } if (!$tab->getType()) { $tab->setType(PHUIListItemView::TYPE_LINK); } $this->tabs[$key] = $tab; } } $this->propertyLists[$key][] = $property_list; $action_list = $property_list->getActionList(); if ($action_list) { $this->actionListID = celerity_generate_unique_node_id(); $action_list->setId($this->actionListID); } return $this; } public function setHeaderText($text) { $this->headerText = $text; return $this; } public function setColor($color) { $this->color = $color; return $this; } public function setFormErrors(array $errors, $title = null) { if ($errors) { $this->formErrors = id(new PHUIInfoView()) ->setTitle($title) ->setErrors($errors); } return $this; } public function setFormSaved($saved, $text = null) { if (!$text) { $text = pht('Changes saved.'); } if ($saved) { $save = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->appendChild($text); $this->formSaved = $save; } return $this; } public function setInfoView(PHUIInfoView $view) { $this->infoView = $view; return $this; } public function setForm($form) { $this->form = $form; return $this; } public function setID($id) { $this->id = $id; return $this; } public function setHeader($header) { $this->header = $header; return $this; } public function setFlush($flush) { $this->flush = $flush; return $this; } public function setObjectList($list) { $this->objectList = $list; return $this; } public function setTable($table) { $this->collapsed = true; $this->table = $table; return $this; } public function setCollapsed($collapsed) { $this->collapsed = $collapsed; return $this; } public function setAnchor(PhabricatorAnchorView $anchor) { $this->anchor = $anchor; return $this; } public function setShowHide($show, $hide, $content, $href, $open = false) { $this->showAction = $show; $this->hideAction = $hide; $this->showHideContent = $content; $this->showHideHref = $href; $this->showHideOpen = $open; return $this; } public function setValidationException( PhabricatorApplicationTransactionValidationException $ex = null) { $this->validationException = $ex; return $this; } public function render() { - require_celerity_resource('phui-object-box-css'); $header = $this->header; + if ($this->headerText) { $header = id(new PHUIHeaderView()) ->setHeader($this->headerText); } $showhide = null; if ($this->showAction !== null) { + if (!$header) { + $header = id(new PHUIHeaderView()); + } + Javelin::initBehavior('phabricator-reveal-content'); $hide_action_id = celerity_generate_unique_node_id(); $show_action_id = celerity_generate_unique_node_id(); $content_id = celerity_generate_unique_node_id(); $hide_style = ($this->showHideOpen ? 'display: none;': null); $show_style = ($this->showHideOpen ? null : 'display: none;'); $hide_action = id(new PHUIButtonView()) ->setTag('a') ->addSigil('reveal-content') ->setID($hide_action_id) ->setStyle($hide_style) ->setHref($this->showHideHref) ->setMetaData( array( 'hideIDs' => array($hide_action_id), 'showIDs' => array($content_id, $show_action_id), )) ->setText($this->showAction); $show_action = id(new PHUIButtonView()) ->setTag('a') ->addSigil('reveal-content') ->setStyle($show_style) ->setHref('#') ->setID($show_action_id) ->setMetaData( array( 'hideIDs' => array($content_id, $show_action_id), 'showIDs' => array($hide_action_id), )) ->setText($this->hideAction); $header->addActionLink($hide_action); $header->addActionLink($show_action); $showhide = array( phutil_tag( 'div', array( 'class' => 'phui-object-box-hidden-content', 'id' => $content_id, 'style' => $show_style, ), $this->showHideContent), ); } if ($this->actionListID) { $icon_id = celerity_generate_unique_node_id(); $icon = id(new PHUIIconView()) ->setIconFont('fa-bars'); $meta = array( 'map' => array( $this->actionListID => 'phabricator-action-list-toggle', $icon_id => 'phuix-dropdown-open', ), ); $mobile_menu = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Actions')) ->setHref('#') ->setIcon($icon) ->addClass('phui-mobile-menu') ->setID($icon_id) ->addSigil('jx-toggle-class') ->setMetadata($meta); $header->addActionLink($mobile_menu); } $ex = $this->validationException; $exception_errors = null; if ($ex) { $messages = array(); foreach ($ex->getErrors() as $error) { $messages[] = $error->getMessage(); } if ($messages) { $exception_errors = id(new PHUIInfoView()) ->setErrors($messages); } } $tab_lists = array(); $property_lists = array(); $tab_map = array(); $default_key = 'tab.default'; // Find the selected tab, or select the first tab if none are selected. if ($this->tabs) { $selected_tab = null; foreach ($this->tabs as $key => $tab) { if ($tab->getSelected()) { $selected_tab = $key; break; } } if ($selected_tab === null) { head($this->tabs)->setSelected(true); $selected_tab = head_key($this->tabs); } } foreach ($this->propertyLists as $key => $list) { $group = new PHUIPropertyGroupView(); $i = 0; foreach ($list as $item) { $group->addPropertyList($item); if ($i > 0) { $item->addClass('phui-property-list-section-noninitial'); } $i++; } if ($this->tabs && $key != $default_key) { $tab_id = celerity_generate_unique_node_id(); $tab_map[$key] = $tab_id; if ($key === $selected_tab) { $style = null; } else { $style = 'display: none'; } $tab_lists[] = phutil_tag( 'div', array( 'style' => $style, 'id' => $tab_id, ), $group); } else { if ($this->tabs) { $group->addClass('phui-property-group-noninitial'); } $property_lists[] = $group; } } $tabs = null; if ($this->tabs) { $tabs = id(new PHUIListView()) ->setType(PHUIListView::NAVBAR_LIST); foreach ($this->tabs as $tab) { $tabs->addMenuItem($tab); } Javelin::initBehavior('phui-object-box-tabs'); } $content = id(new PHUIBoxView()) ->appendChild( array( ($this->showHideOpen == false ? $this->anchor : null), $header, $this->infoView, $this->formErrors, $this->formSaved, $exception_errors, $this->form, $tabs, $tab_lists, $showhide, ($this->showHideOpen == true ? $this->anchor : null), $property_lists, $this->table, $this->renderChildren(), )) ->setBorder(true) ->setID($this->id) ->addMargin(PHUI::MARGIN_LARGE_TOP) ->addMargin(PHUI::MARGIN_LARGE_LEFT) ->addMargin(PHUI::MARGIN_LARGE_RIGHT) ->addClass('phui-object-box'); if ($this->color) { $content->addClass('phui-object-box-'.$this->color); } if ($this->collapsed) { $content->addClass('phui-object-box-collapsed'); } if ($this->tabs) { $content->addSigil('phui-object-box'); $content->setMetadata( array( 'tabMap' => $tab_map, )); } if ($this->flush) { $content->addClass('phui-object-box-flush'); } foreach ($this->sigils as $sigil) { $content->addSigil($sigil); } if ($this->metadata !== null) { $content->setMetadata($this->metadata); } if ($this->objectList) { $content->appendChild($this->objectList); } return $content; } }