diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 60254ddcca..4c726f5884 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -1,301 +1,299 @@ loadProject(); if ($response) { return $response; } $viewer = $request->getUser(); $project = $this->getProject(); $id = $project->getID(); $picture = $project->getProfileImageURI(); $icon = $project->getDisplayIconIcon(); $icon_name = $project->getDisplayIconName(); $tag = id(new PHUITagView()) ->setIcon($icon) ->setName($icon_name) ->addClass('project-view-header-tag') ->setType(PHUITagView::TYPE_SHADE); $header = id(new PHUIHeaderView()) ->setHeader(array($project->getDisplayName(), $tag)) ->setUser($viewer) ->setPolicyObject($project) ->setProfileHeader(true); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } else { $header->setStatus('fa-ban', 'red', pht('Archived')); } $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); if ($can_edit) { $header->setImageEditURL($this->getApplicationURI("picture/{$id}/")); } $properties = $this->buildPropertyListView($project); $watch_action = $this->renderWatchAction($project); $header->addActionLink($watch_action); $milestone_list = $this->buildMilestoneList($project); $subproject_list = $this->buildSubprojectList($project); $member_list = id(new PhabricatorProjectMemberListView()) ->setUser($viewer) ->setProject($project) ->setLimit(5) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setUserPHIDs($project->getMemberPHIDs()); $watcher_list = id(new PhabricatorProjectWatcherListView()) ->setUser($viewer) ->setProject($project) ->setLimit(5) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setUserPHIDs($project->getWatcherPHIDs()); $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::ITEM_PROFILE); $stories = id(new PhabricatorFeedQuery()) ->setViewer($viewer) ->setFilterPHIDs( array( $project->getPHID(), )) ->setLimit(50) ->execute(); $feed = $this->renderStories($stories); $feed = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Recent Activity')) ->addClass('project-view-feed') ->appendChild($feed); require_celerity_resource('project-view-css'); $home = id(new PHUITwoColumnView()) ->setHeader($header) ->addClass('project-view-home') ->addClass('project-view-people-home') ->setMainColumn( array( $properties, $feed, )) ->setSideColumn( array( $milestone_list, $subproject_list, $member_list, $watcher_list, )); $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); return $this->newPage() ->setNavigation($nav) ->setCrumbs($crumbs) ->setTitle($project->getDisplayName()) ->setPageObjectPHIDs(array($project->getPHID())) ->appendChild( array( $home, )); } private function buildPropertyListView( PhabricatorProject $project) { $request = $this->getRequest(); $viewer = $request->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($project); $field_list = PhabricatorCustomField::getObjectFields( $project, PhabricatorCustomField::ROLE_VIEW); $field_list->appendFieldsToPropertyList($project, $viewer, $view); if (!$view->hasAnyProperties()) { return null; } $header = id(new PHUIHeaderView()) ->setHeader(pht('Properties')); $view = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($view) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addClass('project-view-properties'); return $view; } private function renderStories(array $stories) { assert_instances_of($stories, 'PhabricatorFeedStory'); $builder = new PhabricatorFeedBuilder($stories); $builder->setUser($this->getRequest()->getUser()); $builder->setShowHovercards(true); $view = $builder->buildView(); return $view; } private function renderWatchAction(PhabricatorProject $project) { $viewer = $this->getViewer(); $id = $project->getID(); if (!$viewer->isLoggedIn()) { $is_watcher = false; $is_ancestor = false; } else { $viewer_phid = $viewer->getPHID(); $is_watcher = $project->isUserWatcher($viewer_phid); $is_ancestor = $project->isUserAncestorWatcher($viewer_phid); } if ($is_ancestor && !$is_watcher) { $watch_icon = 'fa-eye'; $watch_text = pht('Watching Ancestor'); $watch_href = "/project/watch/{$id}/?via=profile"; $watch_disabled = true; } else if (!$is_watcher) { $watch_icon = 'fa-eye'; $watch_text = pht('Watch Project'); $watch_href = "/project/watch/{$id}/?via=profile"; $watch_disabled = false; } else { $watch_icon = 'fa-eye-slash'; $watch_text = pht('Unwatch Project'); $watch_href = "/project/unwatch/{$id}/?via=profile"; $watch_disabled = false; } $watch_icon = id(new PHUIIconView()) ->setIcon($watch_icon); return id(new PHUIButtonView()) ->setTag('a') ->setWorkflow(true) ->setIcon($watch_icon) ->setText($watch_text) ->setHref($watch_href) ->setDisabled($watch_disabled); } private function buildMilestoneList(PhabricatorProject $project) { if (!$project->getHasMilestones()) { return null; } $viewer = $this->getViewer(); $id = $project->getID(); $milestones = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) ->withIsMilestone(true) ->withStatuses( array( PhabricatorProjectStatus::STATUS_ACTIVE, )) ->setOrderVector(array('milestoneNumber', 'id')) ->execute(); if (!$milestones) { return null; } $milestone_list = id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($milestones) ->renderList(); $view_all = id(new PHUIButtonView()) ->setTag('a') ->setIcon( id(new PHUIIconView()) ->setIcon('fa-list-ul')) ->setText(pht('View All')) ->setHref("/project/subprojects/{$id}/"); $header = id(new PHUIHeaderView()) ->setHeader(pht('Milestones')) ->addActionLink($view_all); return id(new PHUIObjectBoxView()) ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($milestone_list); } private function buildSubprojectList(PhabricatorProject $project) { if (!$project->getHasSubprojects()) { return null; } $viewer = $this->getViewer(); $id = $project->getID(); $limit = 25; $subprojects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) - ->needMembers(true) - ->needWatchers(true) ->withStatuses( array( PhabricatorProjectStatus::STATUS_ACTIVE, )) ->withIsMilestone(false) ->setLimit($limit) ->execute(); if (!$subprojects) { return null; } $subproject_list = id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($subprojects) ->renderList(); $view_all = id(new PHUIButtonView()) ->setTag('a') ->setIcon( id(new PHUIIconView()) ->setIcon('fa-list-ul')) ->setText(pht('View All')) ->setHref("/project/subprojects/{$id}/"); $header = id(new PHUIHeaderView()) ->setHeader(pht('Subprojects')) ->addActionLink($view_all); return id(new PHUIObjectBoxView()) ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($subproject_list); } } diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index a25a41fad0..4a89bb4cde 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -1,266 +1,262 @@ getViewer(); $response = $this->loadProject(); if ($response) { return $response; } $project = $this->getProject(); $id = $project->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); $allows_subprojects = $project->supportsSubprojects(); $allows_milestones = $project->supportsMilestones(); if ($allows_subprojects) { $subprojects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) - ->needMembers(true) - ->needWatchers(true) ->withIsMilestone(false) ->execute(); } else { $subprojects = array(); } if ($allows_milestones) { $milestones = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) - ->needMembers(true) - ->needWatchers(true) ->withIsMilestone(true) ->setOrderVector(array('milestoneNumber', 'id')) ->execute(); } else { $milestones = array(); } if ($milestones) { $milestone_list = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Milestones')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList( id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($milestones) ->renderList()); } else { $milestone_list = null; } if ($subprojects) { $subproject_list = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Subprojects')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList( id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($subprojects) ->renderList()); } else { $subproject_list = null; } $property_list = $this->buildPropertyList( $project, $milestones, $subprojects); $curtain = $this->buildCurtainView( $project, $milestones, $subprojects); $details = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Details')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($property_list); $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::ITEM_SUBPROJECTS); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Subprojects')); $crumbs->setBorder(true); $header = id(new PHUIHeaderView()) ->setHeader(pht('Subprojects and Milestones')) ->setHeaderIcon('fa-sitemap'); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->setMainColumn(array( $details, $milestone_list, $subproject_list, )); return $this->newPage() ->setNavigation($nav) ->setCrumbs($crumbs) ->setTitle(array($project->getName(), pht('Subprojects'))) ->appendChild($view); } private function buildPropertyList( PhabricatorProject $project, array $milestones, array $subprojects) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer); $view->addProperty( pht('Prototype'), $this->renderStatus( 'fa-exclamation-triangle red', pht('Warning'), pht('Subprojects and milestones are only partially implemented.'))); if (!$project->supportsMilestones()) { $milestone_status = $this->renderStatus( 'fa-times grey', pht('Already Milestone'), pht( 'This project is already a milestone, and milestones may not '. 'have their own milestones.')); } else { if (!$milestones) { $milestone_status = $this->renderStatus( 'fa-check grey', pht('None Created'), pht( 'You can create milestones for this project.')); } else { $milestone_status = $this->renderStatus( 'fa-check green', pht('Has Milestones'), pht('This project has milestones.')); } } $view->addProperty(pht('Milestones'), $milestone_status); if (!$project->supportsSubprojects()) { $subproject_status = $this->renderStatus( 'fa-times grey', pht('Milestone'), pht( 'This project is a milestone, and milestones may not have '. 'subprojects.')); } else { if (!$subprojects) { $subproject_status = $this->renderStatus( 'fa-check grey', pht('None Created'), pht('You can create subprojects for this project.')); } else { $subproject_status = $this->renderStatus( 'fa-check green', pht('Has Subprojects'), pht( 'This project has subprojects.')); } } $view->addProperty(pht('Subprojects'), $subproject_status); return $view; } private function buildCurtainView( PhabricatorProject $project, array $milestones, array $subprojects) { $viewer = $this->getViewer(); $id = $project->getID(); $can_create = $this->hasApplicationCapability( ProjectCreateProjectsCapability::CAPABILITY); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); $allows_milestones = $project->supportsMilestones(); $allows_subprojects = $project->supportsSubprojects(); $curtain = $this->newCurtainView($project); if ($allows_milestones && $milestones) { $milestone_text = pht('Create Next Milestone'); } else { $milestone_text = pht('Create Milestone'); } $can_milestone = ($can_create && $can_edit && $allows_milestones); $milestone_href = "/project/edit/?milestone={$id}"; $curtain->addAction( id(new PhabricatorActionView()) ->setName($milestone_text) ->setIcon('fa-plus') ->setHref($milestone_href) ->setDisabled(!$can_milestone) ->setWorkflow(!$can_milestone)); $can_subproject = ($can_create && $can_edit && $allows_subprojects); // If we're offering to create the first subproject, we're going to warn // the user about the effects before moving forward. if ($can_subproject && !$subprojects) { $subproject_href = "/project/warning/{$id}/"; $subproject_disabled = false; $subproject_workflow = true; } else { $subproject_href = "/project/edit/?parent={$id}"; $subproject_disabled = !$can_subproject; $subproject_workflow = !$can_subproject; } $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Create Subproject')) ->setIcon('fa-plus') ->setHref($subproject_href) ->setDisabled($subproject_disabled) ->setWorkflow($subproject_workflow)); return $curtain; } private function renderStatus($icon, $target, $note) { $item = id(new PHUIStatusItemView()) ->setIcon($icon) ->setTarget(phutil_tag('strong', array(), $target)) ->setNote($note); return id(new PHUIStatusListView()) ->addItem($item); } } diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 70931a8b00..df13278812 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -1,259 +1,261 @@ needImages(true) ->needMembers(true) ->needWatchers(true); } protected function buildCustomSearchFields() { return array( id(new PhabricatorSearchTextField()) ->setLabel(pht('Name')) ->setKey('name'), id(new PhabricatorUsersSearchField()) ->setLabel(pht('Members')) ->setKey('memberPHIDs') ->setConduitKey('members') ->setAliases(array('member', 'members')), id(new PhabricatorUsersSearchField()) ->setLabel(pht('Watchers')) ->setKey('watcherPHIDs') ->setConduitKey('watchers') ->setAliases(array('watcher', 'watchers')), id(new PhabricatorSearchSelectField()) ->setLabel(pht('Status')) ->setKey('status') ->setOptions($this->getStatusOptions()), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Milestones')) ->setKey('isMilestone') ->setOptions( pht('(Show All)'), pht('Show Only Milestones'), pht('Hide Milestones')) ->setDescription( pht( 'Pass true to find only milestones, or false to omit '. 'milestones.')), id(new PhabricatorSearchCheckboxesField()) ->setLabel(pht('Icons')) ->setKey('icons') ->setOptions($this->getIconOptions()), id(new PhabricatorSearchCheckboxesField()) ->setLabel(pht('Colors')) ->setKey('colors') ->setOptions($this->getColorOptions()), id(new PhabricatorPHIDsSearchField()) ->setLabel(pht('Parent Projects')) ->setKey('parentPHIDs') ->setConduitKey('parents') ->setAliases(array('parent', 'parents', 'parentPHID')) ->setDescription(pht('Find direct subprojects of specified parents.')), id(new PhabricatorPHIDsSearchField()) ->setLabel(pht('Ancestor Projects')) ->setKey('ancestorPHIDs') ->setConduitKey('ancestors') ->setAliases(array('ancestor', 'ancestors', 'ancestorPHID')) ->setDescription( pht('Find all subprojects beneath specified ancestors.')), ); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); if (strlen($map['name'])) { $tokens = PhabricatorTypeaheadDatasource::tokenizeString($map['name']); $query->withNameTokens($tokens); } if ($map['memberPHIDs']) { $query->withMemberPHIDs($map['memberPHIDs']); } if ($map['watcherPHIDs']) { $query->withWatcherPHIDs($map['watcherPHIDs']); } if ($map['status']) { $status = idx($this->getStatusValues(), $map['status']); if ($status) { $query->withStatus($status); } } if ($map['icons']) { $query->withIcons($map['icons']); } if ($map['colors']) { $query->withColors($map['colors']); } if ($map['isMilestone'] !== null) { $query->withIsMilestone($map['isMilestone']); } if ($map['parentPHIDs']) { $query->withParentProjectPHIDs($map['parentPHIDs']); } if ($map['ancestorPHIDs']) { $query->withAncestorProjectPHIDs($map['ancestorPHIDs']); } return $query; } protected function getURI($path) { return '/project/'.$path; } protected function getBuiltinQueryNames() { $names = array(); if ($this->requireViewer()->isLoggedIn()) { $names['joined'] = pht('Joined'); } if ($this->requireViewer()->isLoggedIn()) { $names['watching'] = pht('Watching'); } $names['active'] = pht('Active'); $names['all'] = pht('All'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer_phid = $this->requireViewer()->getPHID(); // By default, do not show milestones in the list view. $query->setParameter('isMilestone', false); switch ($query_key) { case 'all': return $query; case 'active': return $query ->setParameter('status', 'active'); case 'joined': return $query ->setParameter('memberPHIDs', array($viewer_phid)) ->setParameter('status', 'active'); case 'watching': return $query ->setParameter('watcherPHIDs', array($viewer_phid)) ->setParameter('status', 'active'); } return parent::buildSavedQueryFromBuiltin($query_key); } private function getStatusOptions() { return array( 'active' => pht('Show Only Active Projects'), 'archived' => pht('Show Only Archived Projects'), 'all' => pht('Show All Projects'), ); } private function getStatusValues() { return array( 'active' => PhabricatorProjectQuery::STATUS_ACTIVE, 'archived' => PhabricatorProjectQuery::STATUS_ARCHIVED, 'all' => PhabricatorProjectQuery::STATUS_ANY, ); } private function getIconOptions() { $options = array(); $set = new PhabricatorProjectIconSet(); foreach ($set->getIcons() as $icon) { if ($icon->getIsDisabled()) { continue; } $options[$icon->getKey()] = array( id(new PHUIIconView()) ->setIcon($icon->getIcon()), ' ', $icon->getLabel(), ); } return $options; } private function getColorOptions() { $options = array(); foreach (PhabricatorProjectIconSet::getColorMap() as $color => $name) { $options[$color] = array( id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) ->setShade($color) ->setName($name), ); } return $options; } protected function renderResultList( array $projects, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($projects, 'PhabricatorProject'); $viewer = $this->requireViewer(); $list = id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($projects) + ->setShowWatching(true) + ->setShowMember(true) ->renderList(); return id(new PhabricatorApplicationSearchResultView()) ->setObjectList($list) ->setNoDataString(pht('No projects found.')); } protected function getNewUserBody() { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Project')) ->setHref('/project/edit/') ->setColor(PHUIButtonView::GREEN); $icon = $this->getApplication()->getIcon(); $app_name = $this->getApplication()->getName(); $view = id(new PHUIBigInfoView()) ->setIcon($icon) ->setTitle(pht('Welcome to %s', $app_name)) ->setDescription( pht('Projects are flexible storage containers used as '. 'tags, teams, projects, or anything you need to group.')) ->addAction($create_button); return $view; } } diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php index 38c845167c..994e78ce76 100644 --- a/src/applications/project/view/PhabricatorProjectListView.php +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -1,72 +1,87 @@ projects = $projects; return $this; } public function getProjects() { return $this->projects; } + public function setShowWatching($watching) { + $this->showWatching = $watching; + return $this; + } + + public function setShowMember($member) { + $this->showMember = $member; + return $this; + } + public function renderList() { $viewer = $this->getUser(); $viewer_phid = $viewer->getPHID(); $projects = $this->getProjects(); $handles = $viewer->loadHandles(mpull($projects, 'getPHID')); $list = id(new PHUIObjectItemListView()) ->setUser($viewer); foreach ($projects as $key => $project) { $id = $project->getID(); $icon = $project->getDisplayIconIcon(); $icon_icon = id(new PHUIIconView()) ->setIcon($icon); $icon_name = $project->getDisplayIconName(); $item = id(new PHUIObjectItemView()) ->setHeader($project->getName()) ->setHref("/project/view/{$id}/") ->setImageURI($project->getProfileImageURI()) ->addAttribute( array( $icon_icon, ' ', $icon_name, )); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) { $item->addIcon('delete-grey', pht('Archived')); $item->setDisabled(true); } - $is_member = $project->isUserMember($viewer_phid); - $is_watcher = $project->isUserWatcher($viewer_phid); - - if ($is_member) { - $item->addIcon('fa-user', pht('Member')); + if ($this->showMember) { + $is_member = $project->isUserMember($viewer_phid); + if ($is_member) { + $item->addIcon('fa-user', pht('Member')); + } } - if ($is_watcher) { - $item->addIcon('fa-eye', pht('Watching')); + if ($this->showWatching) { + $is_watcher = $project->isUserWatcher($viewer_phid); + if ($is_watcher) { + $item->addIcon('fa-eye', pht('Watching')); + } } $list->addItem($item); } return $list; } public function render() { return $this->renderList(); } }