1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 00:42:41 +01:00

Move member/watch actions to "Members/Watchers" page

Summary:
Ref T10054. This tries to make the members page a bit more consistent and provide hints to users about subproject/milestone membership rules. In particular:

  - You now join, leave, watch, unwatch, add and remove members, and lock and unlock membership from the members screen.
  - We now explain the membership rule for the project on this screen. There are currently four rules:
    - Normal Project: Join/leave normally.
    - Parent Project: Uses subprojects to determine members.
    - Milestone: Uses parent project to determine members.
    - Locked: Membership is locked.
    - (Future) Imported from LDAP/other external sources: Membership is determined by something else.

Test Plan: {F1064878}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10054

Differential Revision: https://secure.phabricator.com/D15059
This commit is contained in:
epriestley 2016-01-19 16:27:36 -08:00
parent 26500f0545
commit 6b1b21c999
15 changed files with 501 additions and 286 deletions

View file

@ -7,7 +7,7 @@
*/
return array(
'names' => array(
'core.pkg.css' => '7fce81fc',
'core.pkg.css' => 'bd4f3259',
'core.pkg.js' => '573e6664',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9',
@ -143,7 +143,7 @@ return array(
'rsrc/css/phui/phui-object-item-list-view.css' => '26c30d3f',
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
'rsrc/css/phui/phui-profile-menu.css' => '72d69773',
'rsrc/css/phui/phui-profile-menu.css' => '84966ae9',
'rsrc/css/phui/phui-property-list-view.css' => '27b2849e',
'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591',
'rsrc/css/phui/phui-spacing.css' => '042804d6',
@ -819,7 +819,7 @@ return array(
'phui-object-item-list-view-css' => '26c30d3f',
'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e',
'phui-profile-menu-css' => '72d69773',
'phui-profile-menu-css' => '84966ae9',
'phui-property-list-view-css' => '27b2849e',
'phui-remarkup-preview-css' => '1a8f2591',
'phui-spacing-css' => '042804d6',

View file

@ -2895,12 +2895,14 @@ phutil_register_library_map(array(
'PhabricatorProjectLogicalUserDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalUserDatasource.php',
'PhabricatorProjectLogicalViewerDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php',
'PhabricatorProjectMaterializedMemberEdgeType' => 'applications/project/edge/PhabricatorProjectMaterializedMemberEdgeType.php',
'PhabricatorProjectMemberListView' => 'applications/project/view/PhabricatorProjectMemberListView.php',
'PhabricatorProjectMemberOfProjectEdgeType' => 'applications/project/edge/PhabricatorProjectMemberOfProjectEdgeType.php',
'PhabricatorProjectMembersAddController' => 'applications/project/controller/PhabricatorProjectMembersAddController.php',
'PhabricatorProjectMembersDatasource' => 'applications/project/typeahead/PhabricatorProjectMembersDatasource.php',
'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php',
'PhabricatorProjectMembersPolicyRule' => 'applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php',
'PhabricatorProjectMembersProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectMembersProfilePanel.php',
'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php',
'PhabricatorProjectMembersViewController' => 'applications/project/controller/PhabricatorProjectMembersViewController.php',
'PhabricatorProjectMilestonesController' => 'applications/project/controller/PhabricatorProjectMilestonesController.php',
'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php',
'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php',
@ -2932,8 +2934,10 @@ phutil_register_library_map(array(
'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php',
'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php',
'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php',
'PhabricatorProjectUserListView' => 'applications/project/view/PhabricatorProjectUserListView.php',
'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php',
'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php',
'PhabricatorProjectWatcherListView' => 'applications/project/view/PhabricatorProjectWatcherListView.php',
'PhabricatorProjectWorkboardProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php',
'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php',
'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php',
@ -7292,12 +7296,14 @@ phutil_register_library_map(array(
'PhabricatorProjectLogicalUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectLogicalViewerDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorProjectMaterializedMemberEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectMemberListView' => 'PhabricatorProjectUserListView',
'PhabricatorProjectMemberOfProjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectMembersAddController' => 'PhabricatorProjectController',
'PhabricatorProjectMembersDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController',
'PhabricatorProjectMembersPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorProjectMembersProfilePanel' => 'PhabricatorProfilePanel',
'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController',
'PhabricatorProjectMembersViewController' => 'PhabricatorProjectController',
'PhabricatorProjectMilestonesController' => 'PhabricatorProjectController',
'PhabricatorProjectMoveController' => 'PhabricatorProjectController',
'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar',
@ -7332,8 +7338,10 @@ phutil_register_library_map(array(
'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener',
'PhabricatorProjectUpdateController' => 'PhabricatorProjectController',
'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectUserListView' => 'AphrontView',
'PhabricatorProjectViewController' => 'PhabricatorProjectController',
'PhabricatorProjectWatchController' => 'PhabricatorProjectController',
'PhabricatorProjectWatcherListView' => 'PhabricatorProjectUserListView',
'PhabricatorProjectWorkboardProfilePanel' => 'PhabricatorProfilePanel',
'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField',

View file

@ -48,7 +48,9 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
'lock/(?P<id>[1-9]\d*)/'
=> 'PhabricatorProjectLockController',
'members/(?P<id>[1-9]\d*)/'
=> 'PhabricatorProjectMembersEditController',
=> 'PhabricatorProjectMembersViewController',
'members/(?P<id>[1-9]\d*)/add/'
=> 'PhabricatorProjectMembersAddController',
'members/(?P<id>[1-9]\d*)/remove/'
=> 'PhabricatorProjectMembersRemoveController',
'profile/(?P<id>[1-9]\d*)/'

View file

@ -27,7 +27,16 @@ final class PhabricatorProjectLockController
return new Aphront404Response();
}
$done_uri = $project->getURI();
$done_uri = "/project/members/{$id}/";
if (!$project->supportsEditMembers()) {
return $this->newDialog()
->setTitle(pht('Membership Immutable'))
->appendChild(
pht('This project does not support editing membership.'))
->addCancelButton($done_uri);
}
$is_locked = $project->getIsMembershipLocked();
if ($request->isFormPost()) {

View file

@ -0,0 +1,72 @@
<?php
final class PhabricatorProjectMembersAddController
extends PhabricatorProjectController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$this->setProject($project);
if (!$project->supportsEditMembers()) {
return new Aphront404Response();
}
$done_uri = "/project/members/{$id}/";
if ($request->isFormPost()) {
$member_phids = $request->getArr('memberPHIDs');
$type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
$xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $type_member)
->setNewValue(
array(
'+' => array_fuse($member_phids),
));
$editor = id(new PhabricatorProjectTransactionEditor($project))
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($project, $xactions);
return id(new AphrontRedirectResponse())
->setURI($done_uri);
}
$form = id(new AphrontFormView())
->setUser($viewer)
->appendControl(
id(new AphrontFormTokenizerControl())
->setName('memberPHIDs')
->setLabel(pht('Members'))
->setDatasource(new PhabricatorPeopleDatasource()));
return $this->newDialog()
->setTitle(pht('Add Members'))
->appendForm($form)
->addCancelButton($done_uri)
->addSubmitButton(pht('Add Members'));
}
}

View file

@ -1,156 +0,0 @@
<?php
final class PhabricatorProjectMembersEditController
extends PhabricatorProjectController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withIDs(array($id))
->needMembers(true)
->needImages(true)
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$this->setProject($project);
$member_phids = $project->getMemberPHIDs();
if ($request->isFormPost()) {
$member_spec = array();
$remove = $request->getStr('remove');
if ($remove) {
$member_spec['-'] = array_fuse(array($remove));
}
$add_members = $request->getArr('phids');
if ($add_members) {
$member_spec['+'] = array_fuse($add_members);
}
$type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
$xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $type_member)
->setNewValue($member_spec);
$editor = id(new PhabricatorProjectTransactionEditor($project))
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($project, $xactions);
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI());
}
$member_phids = array_reverse($member_phids);
$handles = $this->loadViewerHandles($member_phids);
$state = array();
foreach ($handles as $handle) {
$state[] = array(
'phid' => $handle->getPHID(),
'name' => $handle->getFullName(),
);
}
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$supports_edit = $project->supportsEditMembers();
$form_box = null;
$title = pht('Add Members');
if ($can_edit && $supports_edit) {
$header_name = pht('Edit Members');
$view_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
$form = new AphrontFormView();
$form
->setUser($viewer)
->appendControl(
id(new AphrontFormTokenizerControl())
->setName('phids')
->setLabel(pht('Add Members'))
->setDatasource(new PhabricatorPeopleDatasource()))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($view_uri)
->setValue(pht('Add Members')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setForm($form);
}
$member_list = $this->renderMemberList($project, $handles);
$nav = $this->getProfileMenu();
$nav->selectFilter(PhabricatorProject::PANEL_MEMBERS);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Members'));
return $this->newPage()
->setNavigation($nav)
->setCrumbs($crumbs)
->setTitle(array($project->getName(), $title))
->appendChild($form_box)
->appendChild($member_list);
}
private function renderMemberList(
PhabricatorProject $project,
array $handles) {
$request = $this->getRequest();
$viewer = $request->getUser();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$list = id(new PHUIObjectItemListView())
->setNoDataString(pht('This project does not have any members.'));
foreach ($handles as $handle) {
$remove_uri = $this->getApplicationURI(
'/members/'.$project->getID().'/remove/?phid='.$handle->getPHID());
$item = id(new PHUIObjectItemView())
->setHeader($handle->getFullName())
->setHref($handle->getURI())
->setImageURI($handle->getImageURI());
if ($can_edit) {
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-times')
->setName(pht('Remove'))
->setHref($remove_uri)
->setWorkflow(true));
}
$list->addItem($item);
}
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Members'))
->setObjectList($list);
return $box;
}
}

View file

@ -0,0 +1,205 @@
<?php
final class PhabricatorProjectMembersViewController
extends PhabricatorProjectController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withIDs(array($id))
->needMembers(true)
->needWatchers(true)
->needImages(true)
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$this->setProject($project);
$title = pht('Members and Watchers');
$properties = $this->buildProperties($project);
$actions = $this->buildActions($project);
$properties->setActionList($actions);
$object_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->addPropertyList($properties);
$member_list = id(new PhabricatorProjectMemberListView())
->setUser($viewer)
->setProject($project)
->setUserPHIDs($project->getMemberPHIDs());
$watcher_list = id(new PhabricatorProjectWatcherListView())
->setUser($viewer)
->setProject($project)
->setUserPHIDs($project->getWatcherPHIDs());
$nav = $this->getProfileMenu();
$nav->selectFilter(PhabricatorProject::PANEL_MEMBERS);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Members'));
return $this->newPage()
->setNavigation($nav)
->setCrumbs($crumbs)
->setTitle(array($project->getName(), $title))
->appendChild(
array(
$object_box,
$member_list,
$watcher_list,
));
}
private function buildProperties(PhabricatorProject $project) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($project);
if ($project->isMilestone()) {
$icon_key = PhabricatorProjectIconSet::getMilestoneIconKey();
$icon = PhabricatorProjectIconSet::getIconIcon($icon_key);
$target = PhabricatorProjectIconSet::getIconName($icon_key);
$note = pht(
'Members of the parent project are members of this project.');
$show_join = false;
} else if ($project->getHasSubprojects()) {
$icon = 'fa-sitemap';
$target = pht('Parent Project');
$note = pht(
'Members of all subprojects are members of this project.');
$show_join = false;
} else if ($project->getIsMembershipLocked()) {
$icon = 'fa-lock';
$target = pht('Locked Project');
$note = pht(
'Users with access may join this project, but may not leave.');
$show_join = true;
} else {
$icon = 'fa-briefcase';
$target = pht('Normal Project');
$note = pht('Users with access may join and leave this project.');
$show_join = true;
}
$item = id(new PHUIStatusItemView())
->setIcon($icon)
->setTarget(phutil_tag('strong', array(), $target))
->setNote($note);
$status = id(new PHUIStatusListView())
->addItem($item);
$view->addProperty(pht('Membership'), $status);
if ($show_join) {
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$viewer,
$project);
$view->addProperty(
pht('Joinable By'),
$descriptions[PhabricatorPolicyCapability::CAN_JOIN]);
}
return $view;
}
private function buildActions(PhabricatorProject $project) {
$viewer = $this->getViewer();
$id = $project->getID();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$is_locked = $project->getIsMembershipLocked();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$supports_edit = $project->supportsEditMembers();
$can_join = $supports_edit && PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_JOIN);
$can_leave = $supports_edit && (!$is_locked || $can_edit);
if (!$project->isUserMember($viewer->getPHID())) {
$view->addAction(
id(new PhabricatorActionView())
->setHref('/project/update/'.$project->getID().'/join/')
->setIcon('fa-plus')
->setDisabled(!$can_join)
->setWorkflow(true)
->setName(pht('Join Project')));
} else {
$view->addAction(
id(new PhabricatorActionView())
->setHref('/project/update/'.$project->getID().'/leave/')
->setIcon('fa-times')
->setDisabled(!$can_leave)
->setWorkflow(true)
->setName(pht('Leave Project')));
if (!$project->isUserWatcher($viewer->getPHID())) {
$view->addAction(
id(new PhabricatorActionView())
->setWorkflow(true)
->setHref('/project/watch/'.$project->getID().'/')
->setIcon('fa-eye')
->setName(pht('Watch Project')));
} else {
$view->addAction(
id(new PhabricatorActionView())
->setWorkflow(true)
->setHref('/project/unwatch/'.$project->getID().'/')
->setIcon('fa-eye-slash')
->setName(pht('Unwatch Project')));
}
}
$can_add = $can_edit && $supports_edit;
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Add Members'))
->setIcon('fa-user-plus')
->setHref("/project/members/{$id}/add/")
->setWorkflow(true)
->setDisabled(!$can_add));
$can_lock = $can_edit && $supports_edit && $this->hasApplicationCapability(
ProjectCanLockProjectsCapability::CAPABILITY);
if ($is_locked) {
$lock_name = pht('Unlock Project');
$lock_icon = 'fa-unlock';
} else {
$lock_name = pht('Lock Project');
$lock_icon = 'fa-lock';
}
$view->addAction(
id(new PhabricatorActionView())
->setName($lock_name)
->setIcon($lock_icon)
->setHref($this->getApplicationURI("lock/{$id}/"))
->setDisabled(!$can_lock)
->setWorkflow(true));
return $view;
}
}

View file

@ -106,65 +106,6 @@ final class PhabricatorProjectProfileController
->setWorkflow(true));
}
$can_lock = $can_edit && $this->hasApplicationCapability(
ProjectCanLockProjectsCapability::CAPABILITY);
if ($project->getIsMembershipLocked()) {
$lock_name = pht('Unlock Project');
$lock_icon = 'fa-unlock';
} else {
$lock_name = pht('Lock Project');
$lock_icon = 'fa-lock';
}
$view->addAction(
id(new PhabricatorActionView())
->setName($lock_name)
->setIcon($lock_icon)
->setHref($this->getApplicationURI("lock/{$id}/"))
->setDisabled(!$can_lock)
->setWorkflow(true));
$action = null;
if (!$project->isUserMember($viewer->getPHID())) {
$can_join = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_JOIN);
$action = id(new PhabricatorActionView())
->setUser($viewer)
->setRenderAsForm(true)
->setHref('/project/update/'.$project->getID().'/join/')
->setIcon('fa-plus')
->setDisabled(!$can_join)
->setName(pht('Join Project'));
$view->addAction($action);
} else {
$action = id(new PhabricatorActionView())
->setWorkflow(true)
->setHref('/project/update/'.$project->getID().'/leave/')
->setIcon('fa-times')
->setName(pht('Leave Project...'));
$view->addAction($action);
if (!$project->isUserWatcher($viewer->getPHID())) {
$action = id(new PhabricatorActionView())
->setWorkflow(true)
->setHref('/project/watch/'.$project->getID().'/')
->setIcon('fa-eye')
->setName(pht('Watch Project'));
$view->addAction($action);
} else {
$action = id(new PhabricatorActionView())
->setWorkflow(true)
->setHref('/project/unwatch/'.$project->getID().'/')
->setIcon('fa-eye-slash')
->setName(pht('Unwatch Project'));
$view->addAction($action);
}
}
return $view;
}
@ -206,18 +147,10 @@ final class PhabricatorProjectProfileController
->setAsInline(true)
: phutil_tag('em', array(), pht('None')));
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$viewer,
$project);
$view->addProperty(
pht('Looks Like'),
$viewer->renderHandle($project->getPHID())->setAsTag(true));
$view->addProperty(
pht('Joinable By'),
$descriptions[PhabricatorPolicyCapability::CAN_JOIN]);
$field_list = PhabricatorCustomField::getObjectFields(
$project,
PhabricatorCustomField::ROLE_VIEW);

View file

@ -12,14 +12,11 @@ final class PhabricatorProjectUpdateController
PhabricatorPolicyCapability::CAN_VIEW,
);
$process_action = false;
switch ($action) {
case 'join':
$capabilities[] = PhabricatorPolicyCapability::CAN_JOIN;
$process_action = $request->isFormPost();
break;
case 'leave':
$process_action = $request->isDialogFormPost();
break;
default:
return new Aphront404Response();
@ -35,10 +32,13 @@ final class PhabricatorProjectUpdateController
return new Aphront404Response();
}
$project_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
if (!$project->supportsEditMembers()) {
return new Aphront404Response();
}
if ($process_action) {
$done_uri = "/project/members/{$id}/";
if ($request->isFormPost()) {
$edge_action = null;
switch ($action) {
case 'join':
@ -50,6 +50,7 @@ final class PhabricatorProjectUpdateController
}
$type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
$member_spec = array(
$edge_action => array($viewer->getPHID() => $viewer->getPHID()),
);
@ -67,46 +68,47 @@ final class PhabricatorProjectUpdateController
->setContinueOnMissingFields(true)
->applyTransactions($project, $xactions);
return id(new AphrontRedirectResponse())->setURI($project_uri);
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
$dialog = null;
switch ($action) {
case 'leave':
$dialog = new AphrontDialogView();
$dialog->setUser($viewer);
if ($this->userCannotLeave($project)) {
$dialog->setTitle(pht('You can not leave this project.'));
$body = pht('The membership is locked for this project.');
} else {
$dialog->setTitle(pht('Really leave project?'));
$body = pht(
'Your tremendous contributions to this project will be sorely '.
'missed. Are you sure you want to leave?');
$dialog->addSubmitButton(pht('Leave Project'));
}
$dialog->appendParagraph($body);
$dialog->addCancelButton($project_uri);
break;
default:
return new Aphront404Response();
$is_locked = $project->getIsMembershipLocked();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$can_leave = ($can_edit || !$is_locked);
$button = null;
if ($action == 'leave') {
if ($can_leave) {
$title = pht('Leave Project');
$body = pht(
'Your tremendous contributions to this project will be sorely '.
'missed. Are you sure you want to leave?');
$button = pht('Leave Project');
} else {
$title = pht('Membership Locked');
$body = pht(
'Membership for this project is locked. You can not leave.');
}
} else {
$title = pht('Join Project');
$body = pht(
'Join this project? You will become a member and enjoy whatever '.
'benefits membership may confer.');
$button = pht('Join Project');
}
return id(new AphrontDialogResponse())->setDialog($dialog);
$dialog = $this->newDialog()
->setTitle($title)
->appendParagraph($body)
->addCancelButton($done_uri);
if ($button) {
$dialog->addSubmitButton($button);
}
return $dialog;
}
/**
* This is enforced in @{class:PhabricatorProjectTransactionEditor}. We use
* this logic to render a better form for users hitting this case.
*/
private function userCannotLeave(PhabricatorProject $project) {
$viewer = $this->getViewer();
return
$project->getIsMembershipLocked() &&
!PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
}
}

View file

@ -18,9 +18,9 @@ final class PhabricatorProjectWatchController
return new Aphront404Response();
}
$project_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
$done_uri = "/project/members/{$id}/";
// You must be a member of a project to
// You must be a member of a project to watch it.
if (!$project->isUserMember($viewer->getPHID())) {
return new Aphront400Response();
}
@ -56,7 +56,7 @@ final class PhabricatorProjectWatchController
->setContinueOnMissingFields(true)
->applyTransactions($project, $xactions);
return id(new AphrontRedirectResponse())->setURI($project_uri);
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
$dialog = null;
@ -83,7 +83,7 @@ final class PhabricatorProjectWatchController
return $this->newDialog()
->setTitle($title)
->appendParagraph($body)
->addCancelButton($project_uri)
->addCancelButton($done_uri)
->addSubmitButton($submit);
}

View file

@ -0,0 +1,34 @@
<?php
final class PhabricatorProjectMemberListView
extends PhabricatorProjectUserListView {
protected function canEditList() {
$viewer = $this->getUser();
$project = $this->getProject();
if (!$project->supportsEditMembers()) {
return false;
}
return PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
}
protected function getNoDataString() {
return pht('This project does not have any members.');
}
protected function getRemoveURI($phid) {
$project = $this->getProject();
$id = $project->getID();
return "/project/members/{$id}/remove/?phid={$phid}";
}
protected function getHeaderText() {
return pht('Members');
}
}

View file

@ -0,0 +1,78 @@
<?php
abstract class PhabricatorProjectUserListView extends AphrontView {
private $project;
private $userPHIDs;
public function setProject(PhabricatorProject $project) {
$this->project = $project;
return $this;
}
public function getProject() {
return $this->project;
}
public function setUserPHIDs(array $user_phids) {
$this->userPHIDs = $user_phids;
return $this;
}
public function getUserPHIDs() {
return $this->userPHIDs;
}
abstract protected function canEditList();
abstract protected function getNoDataString();
abstract protected function getRemoveURI($phid);
abstract protected function getHeaderText();
public function render() {
$viewer = $this->getUser();
$project = $this->getProject();
$user_phids = $this->getUserPHIDs();
$can_edit = $this->canEditList();
$no_data = $this->getNoDataString();
$list = id(new PHUIObjectItemListView())
->setNoDataString($no_data);
$user_phids = array_reverse($user_phids);
$handles = $viewer->loadHandles($user_phids);
// Always put the viewer first if they are on the list.
$user_phids = array_fuse($user_phids);
$user_phids =
array_select_keys($user_phids, array($viewer->getPHID())) +
$user_phids;
foreach ($user_phids as $user_phid) {
$handle = $handles[$user_phid];
$item = id(new PHUIObjectItemView())
->setHeader($handle->getFullName())
->setHref($handle->getURI())
->setImageURI($handle->getImageURI());
if ($can_edit) {
$remove_uri = $this->getRemoveURI($user_phid);
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-times')
->setName(pht('Remove'))
->setHref($remove_uri)
->setWorkflow(true));
}
$list->addItem($item);
}
return id(new PHUIObjectBoxView())
->setHeaderText($this->getHeaderText())
->setObjectList($list);
}
}

View file

@ -0,0 +1,22 @@
<?php
final class PhabricatorProjectWatcherListView
extends PhabricatorProjectUserListView {
protected function canEditList() {
return false;
}
protected function getNoDataString() {
return pht('This project does not have any watchers.');
}
protected function getRemoveURI($phid) {
return null;
}
protected function getHeaderText() {
return pht('Watchers');
}
}

View file

@ -24,7 +24,6 @@ final class PHUIStatusItemView extends AphrontTagView {
const ICON_CLOCK = 'fa-clock-o';
const ICON_STAR = 'fa-star';
/* render_textarea */
public function setIcon($icon, $color = null, $label = null) {
$this->icon = $icon;
$this->iconLabel = $label;

View file

@ -73,21 +73,24 @@
background-size: 100%;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-list-item-href {
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
.phui-list-item-href {
text-align: center;
padding: 42px 8px 12px;
font-size: 11px;
line-height: 13px;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-list-item-name {
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
.phui-list-item-name {
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-list-item-icon,
.phui-profile-menu .phui-profile-menu-collapsed
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
.phui-list-item-icon,
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
.phui-list-item-href .phui-icon-view {
top: 10px;
left: 29px;
@ -166,27 +169,31 @@
left: 120px;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer {
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
.phui-profile-menu-footer {
width: 40px;
height: {$menu.profile.item.height};
bottom: 0px;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer-1 {
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
.phui-profile-menu-footer-1 {
left: 0;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer-2 {
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
.phui-profile-menu-footer-2 {
left: 40px;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
.phui-profile-menu-footer
.phui-list-item-name {
display: none;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
.phui-profile-menu-footer
.phui-list-item-icon {
top: 10px;
left: 10px;