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

Add a Join / Leave button to Projects

Summary: Make it easy to join or leave (well, slightly less easy) a project.
Publish join/leave to feed. Fix a couple of membership editor bugs.

Test Plan: Joined, left a project.

Reviewers: btrahan, jungejason

Reviewed By: btrahan

CC: aran, epriestley

Maniphest Tasks: T681

Differential Revision: https://secure.phabricator.com/D1485
This commit is contained in:
epriestley 2012-01-25 11:51:20 -08:00
parent add1ae945d
commit cb0bb8165d
13 changed files with 224 additions and 7 deletions

View file

@ -1483,7 +1483,7 @@ celerity_register_resource_map(array(
),
'phabricator-profile-header-css' =>
array(
'uri' => '/res/d39ef6a4/rsrc/css/application/profile/profile-header-view.css',
'uri' => '/res/4b1cb23b/rsrc/css/application/profile/profile-header-view.css',
'type' => 'css',
'requires' =>
array(

View file

@ -628,6 +628,7 @@ phutil_register_library_map(array(
'PhabricatorProjectSubproject' => 'applications/project/storage/subproject',
'PhabricatorProjectTransaction' => 'applications/project/storage/transaction',
'PhabricatorProjectTransactionType' => 'applications/project/constants/transaction',
'PhabricatorProjectUpdateController' => 'applications/project/controller/update',
'PhabricatorRedirectController' => 'applications/base/controller/redirect',
'PhabricatorRefreshCSRFController' => 'applications/auth/controller/refresh',
'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential',
@ -1313,6 +1314,7 @@ phutil_register_library_map(array(
'PhabricatorProjectSubproject' => 'PhabricatorProjectDAO',
'PhabricatorProjectTransaction' => 'PhabricatorProjectDAO',
'PhabricatorProjectTransactionType' => 'PhabricatorProjectConstants',
'PhabricatorProjectUpdateController' => 'PhabricatorProjectController',
'PhabricatorRedirectController' => 'PhabricatorController',
'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController',
'PhabricatorRemarkupRuleDifferential' => 'PhabricatorRemarkupRuleObjectName',

View file

@ -207,6 +207,8 @@ class AphrontDefaultApplicationConfiguration
'affiliation/(?P<id>\d+)/$'
=> 'PhabricatorProjectAffiliationEditController',
'create/$' => 'PhabricatorProjectCreateController',
'update/(?P<id>\d+)/(?P<action>[^/]+)/$'
=> 'PhabricatorProjectUpdateController',
),
'/r(?P<callsign>[A-Z]+)(?P<commit>[a-z0-9]+)$'

View file

@ -1,7 +1,7 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -77,4 +77,12 @@ abstract class PhabricatorFeedStory {
return $this->getStoryData()->getEpoch();
}
final protected function renderHandleList(array $phids) {
$list = array();
foreach ($phids as $phid) {
$list[] = '<strong>'.$this->getHandle($phid)->renderLink().'</strong>';
}
return implode(', ', $list);
}
}

View file

@ -40,7 +40,9 @@ class PhabricatorFeedStoryProject extends PhabricatorFeedStory {
$old = $data->getValue('old');
$new = $data->getValue('new');
$proj = $this->getHandle($data->getValue('projectPHID'));
$auth = $this->getHandle($data->getAuthorPHID());
$author_phid = $data->getAuthorPHID();
$author = $this->getHandle($author_phid);
switch ($type) {
case PhabricatorProjectTransactionType::TYPE_NAME:
@ -58,11 +60,36 @@ class PhabricatorFeedStoryProject extends PhabricatorFeedStory {
'<strong>'.phutil_escape_html($new).'</strong>).';
}
break;
case PhabricatorProjectTransactionType::TYPE_MEMBERS:
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
if ((count($add) == 1) && (count($rem) == 0) &&
(head($add) == $author_phid)) {
$action = 'joined project <strong>'.$proj->renderLink().'</strong>.';
} else if ((count($add) == 0) && (count($rem) == 1) &&
(head($rem) == $author_phid)) {
$action = 'left project <strong>'.$proj->renderLink().'</strong>.';
} else if (empty($rem)) {
$action = 'added members to project '.
'<strong>'.$proj->renderLink().'</strong>: '.
$this->renderHandleList($add).'.';
} else if (empty($add)) {
$action = 'removed members from project '.
'<strong>'.$proj->renderLink().'</strong>: '.
$this->renderHandleList($rem).'.';
} else {
$action = 'changed members of project '.
'<strong>'.$proj->renderLink().'</strong>, added: '.
$this->renderHandleList($add).'; removed: '.
$this->renderHandleList($rem).'.';
}
break;
default:
$action = 'updated project <strong>'.$proj->renderLink().'</strong>';
$action = 'updated project <strong>'.$proj->renderLink().'</strong>.';
break;
}
$view->setTitle('<strong>'.$auth->renderLink().'</strong> '.$action);
$view->setTitle('<strong>'.$author->renderLink().'</strong> '.$action);
$view->setOneLineStory(true);
return $view;

View file

@ -11,6 +11,7 @@ phutil_require_module('phabricator', 'applications/feed/view/story');
phutil_require_module('phabricator', 'applications/project/constants/transaction');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorFeedStoryProject.php');

View file

@ -52,7 +52,7 @@ class PhabricatorProjectProfileController
$picture = null;
}
$members = mpull($project->loadAffiliations(), null, 'getUserPHID');
$nav_view = new AphrontSideNavFilterView();
$uri = new PhutilURI('/project/view/'.$project->getID().'/');
@ -114,6 +114,33 @@ class PhabricatorProjectProfileController
phutil_utf8_shorten($profile->getBlurb(), 1024));
$header->setProfilePicture($picture);
$action = null;
if (empty($members[$user->getPHID()])) {
$action = phabricator_render_form(
$user,
array(
'action' => '/project/update/'.$project->getID().'/join/',
'method' => 'post',
),
phutil_render_tag(
'button',
array(
'class' => 'green',
),
'Join Project'));
} else {
$action = javelin_render_tag(
'a',
array(
'href' => '/project/update/'.$project->getID().'/leave/',
'sigil' => 'workflow',
'class' => 'grey button',
),
'Leave Project...');
}
$header->addAction($action);
$header->appendChild($nav_view);
return $this->buildStandardPageResponse(

View file

@ -18,6 +18,7 @@ phutil_require_module('phabricator', 'applications/project/controller/base');
phutil_require_module('phabricator', 'applications/project/storage/profile');
phutil_require_module('phabricator', 'applications/project/storage/project');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
phutil_require_module('phabricator', 'view/control/table');
phutil_require_module('phabricator', 'view/layout/profileheader');
phutil_require_module('phabricator', 'view/layout/sidenavfilter');

View file

@ -0,0 +1,113 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class PhabricatorProjectUpdateController
extends PhabricatorProjectController {
private $id;
private $action;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
$this->action = $data['action'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$project = id(new PhabricatorProject())->load($this->id);
if (!$project) {
return new Aphront404Response();
}
$process_action = false;
switch ($this->action) {
case 'join':
$process_action = $request->isFormPost();
break;
case 'leave':
$process_action = $request->isDialogFormPost();
break;
default:
return new Aphront404Response();
}
$project_uri = '/project/view/'.$project->getID().'/';
if ($process_action) {
$xactions = array();
switch ($this->action) {
case 'join':
$affils = $project->loadAffiliations();
$affils = mpull($affils, null, 'getUserPHID');
if (empty($affils[$user->getPHID()])) {
$affils[$user->getPHID()] = true;
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_MEMBERS);
$xaction->setNewValue(array_keys($affils));
$xactions[] = $xaction;
}
break;
case 'leave':
$affils = $project->loadAffiliations();
$affils = mpull($affils, null, 'getUserPHID');
if (isset($affils[$user->getPHID()])) {
unset($affils[$user->getPHID()]);
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_MEMBERS);
$xaction->setNewValue(array_keys($affils));
$xactions[] = $xaction;
}
break;
}
if ($xactions) {
$editor = new PhabricatorProjectEditor($project);
$editor->setUser($user);
$editor->applyTransactions($xactions);
}
return id(new AphrontRedirectResponse())->setURI($project_uri);
}
$dialog = null;
switch ($this->action) {
case 'leave':
$dialog = new AphrontDialogView();
$dialog->setUser($user);
$dialog->setTitle('Really leave project?');
$dialog->appendChild(
'<p>Your tremendous contributions to this project will be sorely '.
'missed. Are you sure you want to leave?</p>');
$dialog->addCancelButton($project_uri);
$dialog->addSubmitButton('Leave Project');
break;
default:
return new Aphront404Response();
}
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -0,0 +1,22 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'aphront/response/dialog');
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/project/constants/transaction');
phutil_require_module('phabricator', 'applications/project/controller/base');
phutil_require_module('phabricator', 'applications/project/editor/project');
phutil_require_module('phabricator', 'applications/project/storage/project');
phutil_require_module('phabricator', 'applications/project/storage/transaction');
phutil_require_module('phabricator', 'view/dialog');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorProjectUpdateController.php');

View file

@ -134,7 +134,7 @@ final class PhabricatorProjectEditor {
$old_value = mpull($affils, 'getUserPHID');
$old_value = array_values($old_value);
$xaction->setOldValue($affils);
$xaction->setOldValue($old_value);
$new_value = $xaction->getNewValue();
$new_value = array_filter($new_value);
@ -174,6 +174,7 @@ final class PhabricatorProjectEditor {
foreach ($new as $phid => $ignored) {
if (empty($old[$phid])) {
$affil = new PhabricatorProjectAffiliation();
$affil->setRole('');
$affil->setUserPHID($phid);
$add[] = $affil;
}

View file

@ -21,6 +21,7 @@ final class PhabricatorProfileHeaderView extends AphrontView {
protected $profilePicture;
protected $profileName;
protected $profileDescription;
protected $profileActions = array();
public function setProfilePicture($picture) {
$this->profilePicture = $picture;
@ -37,6 +38,11 @@ final class PhabricatorProfileHeaderView extends AphrontView {
return $this;
}
public function addAction($action) {
$this->profileActions[] = $action;
return $this;
}
public function render() {
require_celerity_resource('phabricator-profile-header-css');
@ -57,6 +63,9 @@ final class PhabricatorProfileHeaderView extends AphrontView {
<td class="profile-header-name">'.
phutil_escape_html($this->profileName).
'</td>
<td class="profile-header-actions" rowspan="2">'.
self::renderSingleView($this->profileActions).
'</td>
<td class="profile-header-picture" rowspan="2">'.
$image.
'</td>

View file

@ -15,6 +15,10 @@
width: 100%;
}
.phabricator-profile-header .profile-header-actions {
padding: 12px;
}
.phabricator-profile-header .profile-header-picture-frame {
margin: 11px;
width: 50px;