mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-13 00:01:03 +01:00
Make project membership edits use transactions and PHIDs and not be awful
Summary: - Split project profile editing apart from project membership editing. - Make project membership editing simpler and easier to use. - Drop role / owner stuff from the UI. Test Plan: Added and removed project members. Edited project profile information. Reviewers: vrana, btrahan Reviewed By: vrana CC: aran Maniphest Tasks: T603 Differential Revision: https://secure.phabricator.com/D3184
This commit is contained in:
parent
f01c89f8de
commit
d5a0352fd7
11 changed files with 319 additions and 325 deletions
|
@ -1621,24 +1621,6 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'disk' => '/rsrc/js/application/phriction/phriction-document-preview.js',
|
'disk' => '/rsrc/js/application/phriction/phriction-document-preview.js',
|
||||||
),
|
),
|
||||||
'javelin-behavior-projects-resource-editor' =>
|
|
||||||
array(
|
|
||||||
'uri' => '/res/ffdde7d9/rsrc/js/application/projects/projects-resource-editor.js',
|
|
||||||
'type' => 'js',
|
|
||||||
'requires' =>
|
|
||||||
array(
|
|
||||||
0 => 'javelin-behavior',
|
|
||||||
1 => 'phabricator-prefab',
|
|
||||||
2 => 'multirow-row-manager',
|
|
||||||
3 => 'javelin-tokenizer',
|
|
||||||
4 => 'javelin-typeahead-preloaded-source',
|
|
||||||
5 => 'javelin-typeahead',
|
|
||||||
6 => 'javelin-dom',
|
|
||||||
7 => 'javelin-json',
|
|
||||||
8 => 'javelin-util',
|
|
||||||
),
|
|
||||||
'disk' => '/rsrc/js/application/projects/projects-resource-editor.js',
|
|
||||||
),
|
|
||||||
'javelin-behavior-refresh-csrf' =>
|
'javelin-behavior-refresh-csrf' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/88beba4c/rsrc/js/application/core/behavior-refresh-csrf.js',
|
'uri' => '/res/88beba4c/rsrc/js/application/core/behavior-refresh-csrf.js',
|
||||||
|
@ -2439,6 +2421,15 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'disk' => '/rsrc/css/application/base/notification-menu.css',
|
'disk' => '/rsrc/css/application/base/notification-menu.css',
|
||||||
),
|
),
|
||||||
|
'phabricator-object-list-view-css' =>
|
||||||
|
array(
|
||||||
|
'uri' => '/res/4e060838/rsrc/css/application/projects/phabricator-object-list-view.css',
|
||||||
|
'type' => 'css',
|
||||||
|
'requires' =>
|
||||||
|
array(
|
||||||
|
),
|
||||||
|
'disk' => '/rsrc/css/application/projects/phabricator-object-list-view.css',
|
||||||
|
),
|
||||||
'phabricator-object-selector-css' =>
|
'phabricator-object-selector-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/7eb4c705/rsrc/css/application/objectselector/object-selector.css',
|
'uri' => '/res/7eb4c705/rsrc/css/application/objectselector/object-selector.css',
|
||||||
|
@ -2739,15 +2730,6 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'disk' => '/rsrc/css/application/phriction/phriction-document-css.css',
|
'disk' => '/rsrc/css/application/phriction/phriction-document-css.css',
|
||||||
),
|
),
|
||||||
'project-edit-css' =>
|
|
||||||
array(
|
|
||||||
'uri' => '/res/c192b5f9/rsrc/css/application/projects/project-edit.css',
|
|
||||||
'type' => 'css',
|
|
||||||
'requires' =>
|
|
||||||
array(
|
|
||||||
),
|
|
||||||
'disk' => '/rsrc/css/application/projects/project-edit.css',
|
|
||||||
),
|
|
||||||
'raphael-core' =>
|
'raphael-core' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/3f48575a/rsrc/js/raphael/raphael.js',
|
'uri' => '/res/3f48575a/rsrc/js/raphael/raphael.js',
|
||||||
|
|
|
@ -852,6 +852,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorObjectHandleConstants' => 'applications/phid/handle/const/PhabricatorObjectHandleConstants.php',
|
'PhabricatorObjectHandleConstants' => 'applications/phid/handle/const/PhabricatorObjectHandleConstants.php',
|
||||||
'PhabricatorObjectHandleData' => 'applications/phid/handle/PhabricatorObjectHandleData.php',
|
'PhabricatorObjectHandleData' => 'applications/phid/handle/PhabricatorObjectHandleData.php',
|
||||||
'PhabricatorObjectHandleStatus' => 'applications/phid/handle/const/PhabricatorObjectHandleStatus.php',
|
'PhabricatorObjectHandleStatus' => 'applications/phid/handle/const/PhabricatorObjectHandleStatus.php',
|
||||||
|
'PhabricatorObjectListView' => 'view/control/PhabricatorObjectListView.php',
|
||||||
'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php',
|
'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php',
|
||||||
'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php',
|
'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php',
|
||||||
'PhabricatorOwnerPathQuery' => 'applications/owners/query/PhabricatorOwnerPathQuery.php',
|
'PhabricatorOwnerPathQuery' => 'applications/owners/query/PhabricatorOwnerPathQuery.php',
|
||||||
|
@ -900,6 +901,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php',
|
'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php',
|
||||||
'PhabricatorProjectEditor' => 'applications/project/editor/PhabricatorProjectEditor.php',
|
'PhabricatorProjectEditor' => 'applications/project/editor/PhabricatorProjectEditor.php',
|
||||||
'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php',
|
'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php',
|
||||||
|
'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php',
|
||||||
'PhabricatorProjectNameCollisionException' => 'applications/project/exception/PhabricatorProjectNameCollisionException.php',
|
'PhabricatorProjectNameCollisionException' => 'applications/project/exception/PhabricatorProjectNameCollisionException.php',
|
||||||
'PhabricatorProjectProfile' => 'applications/project/storage/PhabricatorProjectProfile.php',
|
'PhabricatorProjectProfile' => 'applications/project/storage/PhabricatorProjectProfile.php',
|
||||||
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
|
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
|
||||||
|
@ -1892,6 +1894,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorOAuthServerTokenController' => 'PhabricatorAuthController',
|
'PhabricatorOAuthServerTokenController' => 'PhabricatorAuthController',
|
||||||
'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController',
|
'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController',
|
||||||
'PhabricatorObjectHandleStatus' => 'PhabricatorObjectHandleConstants',
|
'PhabricatorObjectHandleStatus' => 'PhabricatorObjectHandleConstants',
|
||||||
|
'PhabricatorObjectListView' => 'AphrontView',
|
||||||
'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery',
|
'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery',
|
||||||
'PhabricatorOwnersController' => 'PhabricatorController',
|
'PhabricatorOwnersController' => 'PhabricatorController',
|
||||||
'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO',
|
'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO',
|
||||||
|
@ -1935,6 +1938,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectCreateController' => 'PhabricatorProjectController',
|
'PhabricatorProjectCreateController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorProjectDAO' => 'PhabricatorLiskDAO',
|
'PhabricatorProjectDAO' => 'PhabricatorLiskDAO',
|
||||||
'PhabricatorProjectListController' => 'PhabricatorProjectController',
|
'PhabricatorProjectListController' => 'PhabricatorProjectController',
|
||||||
|
'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorProjectNameCollisionException' => 'Exception',
|
'PhabricatorProjectNameCollisionException' => 'Exception',
|
||||||
'PhabricatorProjectProfile' => 'PhabricatorProjectDAO',
|
'PhabricatorProjectProfile' => 'PhabricatorProjectDAO',
|
||||||
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
|
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
|
||||||
|
|
|
@ -36,6 +36,7 @@ final class PhabricatorApplicationProject extends PhabricatorApplication {
|
||||||
'' => 'PhabricatorProjectListController',
|
'' => 'PhabricatorProjectListController',
|
||||||
'filter/(?P<filter>[^/]+)/' => 'PhabricatorProjectListController',
|
'filter/(?P<filter>[^/]+)/' => 'PhabricatorProjectListController',
|
||||||
'edit/(?P<id>\d+)/' => 'PhabricatorProjectProfileEditController',
|
'edit/(?P<id>\d+)/' => 'PhabricatorProjectProfileEditController',
|
||||||
|
'members/(?P<id>\d+)/' => 'PhabricatorProjectMembersEditController',
|
||||||
'view/(?P<id>\d+)/(?:(?P<page>\w+)/)?'
|
'view/(?P<id>\d+)/(?:(?P<page>\w+)/)?'
|
||||||
=> 'PhabricatorProjectProfileController',
|
=> 'PhabricatorProjectProfileController',
|
||||||
'create/' => 'PhabricatorProjectCreateController',
|
'create/' => 'PhabricatorProjectCreateController',
|
||||||
|
|
|
@ -31,4 +31,33 @@ abstract class PhabricatorProjectController extends PhabricatorController {
|
||||||
return $response->setContent($page->render());
|
return $response->setContent($page->render());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function buildLocalNavigation(PhabricatorProject $project) {
|
||||||
|
$id = $project->getID();
|
||||||
|
|
||||||
|
$nav_view = new AphrontSideNavFilterView();
|
||||||
|
$uri = new PhutilURI('/project/view/'.$id.'/');
|
||||||
|
$nav_view->setBaseURI($uri);
|
||||||
|
|
||||||
|
$external_arrow = "\xE2\x86\x97";
|
||||||
|
$tasks_uri = '/maniphest/view/all/?projects='.$project->getPHID();
|
||||||
|
$slug = PhabricatorSlug::normalize($project->getName());
|
||||||
|
$phriction_uri = '/w/projects/'.$slug;
|
||||||
|
|
||||||
|
$edit_uri = '/project/edit/'.$id.'/';
|
||||||
|
$members_uri = '/project/members/'.$id.'/';
|
||||||
|
|
||||||
|
$nav_view->addFilter('dashboard', 'Dashboard');
|
||||||
|
$nav_view->addSpacer();
|
||||||
|
$nav_view->addFilter('feed', 'Feed');
|
||||||
|
$nav_view->addFilter(null, 'Tasks '.$external_arrow, $tasks_uri);
|
||||||
|
$nav_view->addFilter(null, 'Wiki '.$external_arrow, $phriction_uri);
|
||||||
|
$nav_view->addFilter('people', 'People');
|
||||||
|
$nav_view->addFilter('about', 'About');
|
||||||
|
$nav_view->addSpacer();
|
||||||
|
$nav_view->addFilter('edit', "Edit Project\xE2\x80\xA6", $edit_uri);
|
||||||
|
$nav_view->addFilter('members', "Edit Members\xE2\x80\xA6", $members_uri);
|
||||||
|
|
||||||
|
return $nav_view;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
<?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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
final class PhabricatorProjectMembersEditController
|
||||||
|
extends PhabricatorProjectController {
|
||||||
|
|
||||||
|
public function willProcessRequest(array $data) {
|
||||||
|
$this->id = $data['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processRequest() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
$project = id(new PhabricatorProject())->load($this->id);
|
||||||
|
if (!$project) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
$profile = $project->loadProfile();
|
||||||
|
if (empty($profile)) {
|
||||||
|
$profile = new PhabricatorProjectProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
$member_phids = $project->loadMemberPHIDs();
|
||||||
|
|
||||||
|
$errors = array();
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$changed_something = false;
|
||||||
|
$member_map = array_fill_keys($member_phids, true);
|
||||||
|
|
||||||
|
$remove = $request->getStr('remove');
|
||||||
|
if ($remove) {
|
||||||
|
if (isset($member_map[$remove])) {
|
||||||
|
unset($member_map[$remove]);
|
||||||
|
$changed_something = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$new_members = $request->getArr('phids');
|
||||||
|
foreach ($new_members as $member) {
|
||||||
|
if (empty($member_map[$member])) {
|
||||||
|
$member_map[$member] = true;
|
||||||
|
$changed_something = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$xactions = array();
|
||||||
|
if ($changed_something) {
|
||||||
|
$xaction = new PhabricatorProjectTransaction();
|
||||||
|
$xaction->setTransactionType(
|
||||||
|
PhabricatorProjectTransactionType::TYPE_MEMBERS);
|
||||||
|
$xaction->setNewValue(array_keys($member_map));
|
||||||
|
$xactions[] = $xaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($xactions) {
|
||||||
|
$editor = new PhabricatorProjectEditor($project);
|
||||||
|
$editor->setUser($user);
|
||||||
|
$editor->applyTransactions($xactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())
|
||||||
|
->setURI($request->getRequestURI());
|
||||||
|
}
|
||||||
|
|
||||||
|
$member_phids = array_reverse($member_phids);
|
||||||
|
$handles = id(new PhabricatorObjectHandleData($member_phids))
|
||||||
|
->loadHandles();
|
||||||
|
|
||||||
|
$state = array();
|
||||||
|
foreach ($handles as $handle) {
|
||||||
|
$state[] = array(
|
||||||
|
'phid' => $handle->getPHID(),
|
||||||
|
'name' => $handle->getFullName(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$header_name = 'Edit Members';
|
||||||
|
$title = 'Edit Members';
|
||||||
|
|
||||||
|
$list = $this->renderMemberList($handles);
|
||||||
|
|
||||||
|
$form = new AphrontFormView();
|
||||||
|
$form
|
||||||
|
->setUser($user)
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTokenizerControl())
|
||||||
|
->setName('phids')
|
||||||
|
->setLabel('Add Members')
|
||||||
|
->setDatasource('/typeahead/common/users/'))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormSubmitControl())
|
||||||
|
->addCancelButton('/project/view/'.$project->getID().'/')
|
||||||
|
->setValue('Add Members'));
|
||||||
|
$faux_form = id(new AphrontFormLayoutView())
|
||||||
|
->setBackgroundShading(true)
|
||||||
|
->setPadded(true)
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormInsetView())
|
||||||
|
->setTitle('Current Members ('.count($handles).')')
|
||||||
|
->appendChild($list));
|
||||||
|
|
||||||
|
$panel = new AphrontPanelView();
|
||||||
|
$panel->setHeader($header_name);
|
||||||
|
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
|
||||||
|
$panel->appendChild($form);
|
||||||
|
$panel->appendChild('<br />');
|
||||||
|
$panel->appendChild($faux_form);
|
||||||
|
|
||||||
|
$nav = $this->buildLocalNavigation($project);
|
||||||
|
$nav->selectFilter('members');
|
||||||
|
$nav->appendChild($panel);
|
||||||
|
|
||||||
|
return $this->buildStandardPageResponse(
|
||||||
|
$nav,
|
||||||
|
array(
|
||||||
|
'title' => $title,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderMemberList(array $handles) {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
$list = id(new PhabricatorObjectListView())
|
||||||
|
->setHandles($handles);
|
||||||
|
|
||||||
|
foreach ($handles as $handle) {
|
||||||
|
$hidden_input = phutil_render_tag(
|
||||||
|
'input',
|
||||||
|
array(
|
||||||
|
'type' => 'hidden',
|
||||||
|
'name' => 'remove',
|
||||||
|
'value' => $handle->getPHID(),
|
||||||
|
),
|
||||||
|
'');
|
||||||
|
|
||||||
|
$button = javelin_render_tag(
|
||||||
|
'button',
|
||||||
|
array(
|
||||||
|
'class' => 'grey',
|
||||||
|
),
|
||||||
|
pht('Remove'));
|
||||||
|
|
||||||
|
$list->addButton(
|
||||||
|
$handle,
|
||||||
|
phabricator_render_form(
|
||||||
|
$user,
|
||||||
|
array(
|
||||||
|
'method' => 'POST',
|
||||||
|
'action' => $request->getRequestURI(),
|
||||||
|
),
|
||||||
|
$hidden_input.$button));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,26 +44,7 @@ final class PhabricatorProjectProfileController
|
||||||
$members = $project->loadMemberPHIDs();
|
$members = $project->loadMemberPHIDs();
|
||||||
$member_map = array_fill_keys($members, true);
|
$member_map = array_fill_keys($members, true);
|
||||||
|
|
||||||
$nav_view = new AphrontSideNavFilterView();
|
$nav_view = $this->buildLocalNavigation($project);
|
||||||
$uri = new PhutilURI('/project/view/'.$project->getID().'/');
|
|
||||||
$nav_view->setBaseURI($uri);
|
|
||||||
|
|
||||||
$external_arrow = "\xE2\x86\x97";
|
|
||||||
$tasks_uri = '/maniphest/view/all/?projects='.$project->getPHID();
|
|
||||||
$slug = PhabricatorSlug::normalize($project->getName());
|
|
||||||
$phriction_uri = '/w/projects/'.$slug;
|
|
||||||
|
|
||||||
$edit_uri = '/project/edit/'.$project->getID().'/';
|
|
||||||
|
|
||||||
$nav_view->addFilter('dashboard', 'Dashboard');
|
|
||||||
$nav_view->addSpacer();
|
|
||||||
$nav_view->addFilter('feed', 'Feed');
|
|
||||||
$nav_view->addFilter(null, 'Tasks '.$external_arrow, $tasks_uri);
|
|
||||||
$nav_view->addFilter(null, 'Wiki '.$external_arrow, $phriction_uri);
|
|
||||||
$nav_view->addFilter('people', 'People');
|
|
||||||
$nav_view->addFilter('about', 'About');
|
|
||||||
$nav_view->addSpacer();
|
|
||||||
$nav_view->addFilter(null, "Edit Project\xE2\x80\xA6", $edit_uri);
|
|
||||||
|
|
||||||
$this->page = $nav_view->selectFilter($this->page, 'dashboard');
|
$this->page = $nav_view->selectFilter($this->page, 'dashboard');
|
||||||
|
|
||||||
|
|
|
@ -41,18 +41,13 @@ final class PhabricatorProjectProfileEditController
|
||||||
|
|
||||||
$options = PhabricatorProjectStatus::getStatusMap();
|
$options = PhabricatorProjectStatus::getStatusMap();
|
||||||
|
|
||||||
$affiliations = $project->loadAffiliations();
|
|
||||||
$affiliations = mpull($affiliations, null, 'getUserPHID');
|
|
||||||
|
|
||||||
$supported_formats = PhabricatorFile::getTransformableImageFormats();
|
$supported_formats = PhabricatorFile::getTransformableImageFormats();
|
||||||
|
|
||||||
$e_name = true;
|
$e_name = true;
|
||||||
$e_image = null;
|
$e_image = null;
|
||||||
|
|
||||||
$errors = array();
|
$errors = array();
|
||||||
$state = null;
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$xactions = array();
|
$xactions = array();
|
||||||
$xaction = new PhabricatorProjectTransaction();
|
$xaction = new PhabricatorProjectTransaction();
|
||||||
|
@ -112,94 +107,12 @@ final class PhabricatorProjectProfileEditController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$resources = $request->getStr('resources');
|
|
||||||
$resources = json_decode($resources, true);
|
|
||||||
if (!is_array($resources)) {
|
|
||||||
throw new Exception(
|
|
||||||
"Project resource information was not correctly encoded in the ".
|
|
||||||
"request.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$state = array();
|
|
||||||
foreach ($resources as $resource) {
|
|
||||||
$user_phid = $resource['phid'];
|
|
||||||
if (!$user_phid) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (isset($state[$user_phid])) {
|
|
||||||
// TODO: We should deal with this better -- the user has entered
|
|
||||||
// the same resource more than once.
|
|
||||||
}
|
|
||||||
$state[$user_phid] = array(
|
|
||||||
'phid' => $user_phid,
|
|
||||||
'role' => $resource['role'],
|
|
||||||
'owner' => $resource['owner'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$all_phids = array_merge(array_keys($state), array_keys($affiliations));
|
|
||||||
$all_phids = array_unique($all_phids);
|
|
||||||
|
|
||||||
$delete_affiliations = array();
|
|
||||||
$save_affiliations = array();
|
|
||||||
foreach ($all_phids as $phid) {
|
|
||||||
$old = idx($affiliations, $phid);
|
|
||||||
$new = idx($state, $phid);
|
|
||||||
|
|
||||||
if ($old && !$new) {
|
|
||||||
$delete_affiliations[] = $affiliations[$phid];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$old) {
|
|
||||||
$affil = new PhabricatorProjectAffiliation();
|
|
||||||
$affil->setUserPHID($phid);
|
|
||||||
} else {
|
|
||||||
$affil = $old;
|
|
||||||
}
|
|
||||||
|
|
||||||
$affil->setRole((string)$new['role']);
|
|
||||||
$affil->setIsOwner((int)$new['owner']);
|
|
||||||
|
|
||||||
$save_affiliations[] = $affil;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$errors) {
|
if (!$errors) {
|
||||||
$project->save();
|
$project->save();
|
||||||
$profile->setProjectPHID($project->getPHID());
|
$profile->setProjectPHID($project->getPHID());
|
||||||
$profile->save();
|
$profile->save();
|
||||||
|
|
||||||
foreach ($delete_affiliations as $affil) {
|
|
||||||
$affil->delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($save_affiliations as $save) {
|
|
||||||
$save->setProjectPHID($project->getPHID());
|
|
||||||
$save->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
return id(new AphrontRedirectResponse())
|
return id(new AphrontRedirectResponse())
|
||||||
->setURI('/project/view/'.$project->getID().'/');
|
->setURI('/project/view/'.$project->getID().'/');
|
||||||
} else {
|
|
||||||
$phids = array_keys($state);
|
|
||||||
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
|
|
||||||
foreach ($state as $phid => $info) {
|
|
||||||
$state[$phid]['name'] = $handles[$phid]->getFullName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$phids = mpull($affiliations, 'getUserPHID');
|
|
||||||
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
|
|
||||||
|
|
||||||
$state = array();
|
|
||||||
foreach ($affiliations as $affil) {
|
|
||||||
$user_phid = $affil->getUserPHID();
|
|
||||||
$state[] = array(
|
|
||||||
'phid' => $user_phid,
|
|
||||||
'name' => $handles[$user_phid]->getFullName(),
|
|
||||||
'role' => $affil->getRole(),
|
|
||||||
'owner' => $affil->getIsOwner(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,8 +127,6 @@ final class PhabricatorProjectProfileEditController
|
||||||
$title = 'Edit Project';
|
$title = 'Edit Project';
|
||||||
$action = '/project/edit/'.$project->getID().'/';
|
$action = '/project/edit/'.$project->getID().'/';
|
||||||
|
|
||||||
require_celerity_resource('project-edit-css');
|
|
||||||
|
|
||||||
$form = new AphrontFormView();
|
$form = new AphrontFormView();
|
||||||
$form
|
$form
|
||||||
->setID('project-edit-form')
|
->setID('project-edit-form')
|
||||||
|
@ -254,61 +165,26 @@ final class PhabricatorProjectProfileEditController
|
||||||
->setName('image')
|
->setName('image')
|
||||||
->setError($e_image)
|
->setError($e_image)
|
||||||
->setCaption('Supported formats: '.implode(', ', $supported_formats)))
|
->setCaption('Supported formats: '.implode(', ', $supported_formats)))
|
||||||
->appendChild(
|
|
||||||
id(new AphrontFormInsetView())
|
|
||||||
->setTitle('Resources')
|
|
||||||
->setRightButton(javelin_render_tag(
|
|
||||||
'a',
|
|
||||||
array(
|
|
||||||
'href' => '#',
|
|
||||||
'class' => 'button green',
|
|
||||||
'sigil' => 'add-resource',
|
|
||||||
'mustcapture' => true,
|
|
||||||
),
|
|
||||||
'Add New Resource'))
|
|
||||||
->appendChild(
|
|
||||||
phutil_render_tag(
|
|
||||||
'input',
|
|
||||||
array(
|
|
||||||
'type' => 'hidden',
|
|
||||||
'name' => 'resources',
|
|
||||||
'id' => 'resources',
|
|
||||||
)))
|
|
||||||
->setContent(javelin_render_tag(
|
|
||||||
'table',
|
|
||||||
array(
|
|
||||||
'sigil' => 'resources',
|
|
||||||
'class' => 'project-resource-table',
|
|
||||||
),
|
|
||||||
'')))
|
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormSubmitControl())
|
id(new AphrontFormSubmitControl())
|
||||||
->addCancelButton('/project/view/'.$project->getID().'/')
|
->addCancelButton('/project/view/'.$project->getID().'/')
|
||||||
->setValue('Save'));
|
->setValue('Save'));
|
||||||
|
|
||||||
$template = new AphrontTokenizerTemplateView();
|
|
||||||
$template = $template->render();
|
|
||||||
|
|
||||||
Javelin::initBehavior(
|
|
||||||
'projects-resource-editor',
|
|
||||||
array(
|
|
||||||
'root' => 'project-edit-form',
|
|
||||||
'tokenizerTemplate' => $template,
|
|
||||||
'tokenizerSource' => '/typeahead/common/users/',
|
|
||||||
'input' => 'resources',
|
|
||||||
'state' => array_values($state),
|
|
||||||
));
|
|
||||||
|
|
||||||
$panel = new AphrontPanelView();
|
$panel = new AphrontPanelView();
|
||||||
$panel->setHeader($header_name);
|
$panel->setHeader($header_name);
|
||||||
$panel->setWidth(AphrontPanelView::WIDTH_WIDE);
|
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
|
||||||
$panel->appendChild($form);
|
$panel->appendChild($form);
|
||||||
|
|
||||||
return $this->buildStandardPageResponse(
|
$nav = $this->buildLocalNavigation($project);
|
||||||
|
$nav->selectFilter('edit');
|
||||||
|
$nav->appendChild(
|
||||||
array(
|
array(
|
||||||
$error_view,
|
$error_view,
|
||||||
$panel,
|
$panel,
|
||||||
),
|
));
|
||||||
|
|
||||||
|
return $this->buildStandardPageResponse(
|
||||||
|
$nav,
|
||||||
array(
|
array(
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
));
|
));
|
||||||
|
|
68
src/view/control/PhabricatorObjectListView.php
Normal file
68
src/view/control/PhabricatorObjectListView.php
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<?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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
final class PhabricatorObjectListView extends AphrontView {
|
||||||
|
|
||||||
|
private $handles = array();
|
||||||
|
private $buttons = array();
|
||||||
|
|
||||||
|
public function setHandles(array $handles) {
|
||||||
|
assert_instances_of($handles, 'PhabricatorObjectHandle');
|
||||||
|
|
||||||
|
$this->handles = $handles;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addButton(PhabricatorObjectHandle $handle, $button) {
|
||||||
|
$this->buttons[$handle->getPHID()][] = $button;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$handles = $this->handles;
|
||||||
|
|
||||||
|
require_celerity_resource('phabricator-object-list-view-css');
|
||||||
|
|
||||||
|
$out = array();
|
||||||
|
foreach ($handles as $handle) {
|
||||||
|
$buttons = idx($this->buttons, $handle->getPHID(), array());
|
||||||
|
if ($buttons) {
|
||||||
|
$buttons =
|
||||||
|
'<div class="phabricator-object-list-view-buttons">'.
|
||||||
|
implode('', $buttons).
|
||||||
|
'</div>';
|
||||||
|
} else {
|
||||||
|
$buttons = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$out[] = javelin_render_tag(
|
||||||
|
'div',
|
||||||
|
array(
|
||||||
|
'class' => 'phabricator-object-list-view-item',
|
||||||
|
'style' => 'background-image: url('.$handle->getImageURI().');',
|
||||||
|
),
|
||||||
|
$handle->renderLink().$buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
'<div class="phabricator-object-list-view">'.
|
||||||
|
implode("\n", $out).
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* @provides phabricator-object-list-view-css
|
||||||
|
*/
|
||||||
|
|
||||||
|
.phabricator-object-list-view {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-object-list-view-item {
|
||||||
|
line-height: 60px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 5px 5px;
|
||||||
|
padding-left: 70px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-object-list-view-item + .phabricator-object-list-view-item {
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-object-list-view-buttons {
|
||||||
|
position: absolute;
|
||||||
|
text-align: right;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
/**
|
|
||||||
* @provides project-edit-css
|
|
||||||
*/
|
|
||||||
|
|
||||||
.project-resource-table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-resource-table td {
|
|
||||||
padding: 2px 4px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-resource-table td label {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #666666;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-resource-table td.user-tokenizer {
|
|
||||||
width: 35%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-resource-table td.role-label {
|
|
||||||
padding-left: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-resource-table td.role input {
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
/**
|
|
||||||
* @requires javelin-behavior
|
|
||||||
* phabricator-prefab
|
|
||||||
* multirow-row-manager
|
|
||||||
* javelin-tokenizer
|
|
||||||
* javelin-typeahead-preloaded-source
|
|
||||||
* javelin-typeahead
|
|
||||||
* javelin-dom
|
|
||||||
* javelin-json
|
|
||||||
* javelin-util
|
|
||||||
* @provides javelin-behavior-projects-resource-editor
|
|
||||||
* @javelin
|
|
||||||
*/
|
|
||||||
|
|
||||||
JX.behavior('projects-resource-editor', function(config) {
|
|
||||||
|
|
||||||
var root = JX.$(config.root);
|
|
||||||
var resources_table = JX.DOM.find(root, 'table', 'resources');
|
|
||||||
var manager = new JX.MultirowRowManager(resources_table);
|
|
||||||
var resource_rows = [];
|
|
||||||
|
|
||||||
for (var ii = 0; ii < config.state.length; ii++) {
|
|
||||||
addRow(config.state[ii]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderRow(data) {
|
|
||||||
|
|
||||||
var template = JX.$N('div', JX.$H(config.tokenizerTemplate)).firstChild;
|
|
||||||
template.id = '';
|
|
||||||
var datasource = new JX.TypeaheadPreloadedSource(
|
|
||||||
config.tokenizerSource);
|
|
||||||
var typeahead = new JX.Typeahead(template);
|
|
||||||
typeahead.setDatasource(datasource);
|
|
||||||
var tokenizer = new JX.Tokenizer(template);
|
|
||||||
tokenizer.setTypeahead(typeahead);
|
|
||||||
tokenizer.setLimit(1);
|
|
||||||
tokenizer.start();
|
|
||||||
|
|
||||||
if (data.phid) {
|
|
||||||
tokenizer.addToken(data.phid, data.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
var role = JX.$N('input', {type: 'text', value : data.role || ''});
|
|
||||||
|
|
||||||
var ownership = JX.Prefab.renderSelect(
|
|
||||||
{0 : 'Nonowner', 1 : 'Owner'},
|
|
||||||
data.owner || 0);
|
|
||||||
|
|
||||||
var as_object = function() {
|
|
||||||
var tokens = tokenizer.getTokens();
|
|
||||||
return {
|
|
||||||
phid : JX.keys(tokens)[0] || null,
|
|
||||||
role : role.value,
|
|
||||||
owner : ownership.value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var r = [];
|
|
||||||
r.push([null, JX.$N('label', {}, 'User:')]);
|
|
||||||
r.push(['user-tokenizer', template]);
|
|
||||||
r.push(['role-label', JX.$N('label', {}, 'Role:')]);
|
|
||||||
r.push(['role', role]);
|
|
||||||
r.push([null, ownership]);
|
|
||||||
|
|
||||||
for (var ii = 0; ii < r.length; ii++) {
|
|
||||||
r[ii] = JX.$N('td', {className : r[ii][0]}, r[ii][1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
nodes : r,
|
|
||||||
dataCallback : as_object
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function onaddresource(e) {
|
|
||||||
e.kill();
|
|
||||||
addRow({});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addRow(info) {
|
|
||||||
var data = renderRow(info);
|
|
||||||
var row = manager.addRow(data.nodes);
|
|
||||||
var id = manager.getRowID(row);
|
|
||||||
|
|
||||||
resource_rows[id] = data.dataCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onsubmit(e) {
|
|
||||||
var result = [];
|
|
||||||
for (var ii = 0; ii < resource_rows.length; ii++) {
|
|
||||||
if (resource_rows[ii]) {
|
|
||||||
var obj = resource_rows[ii]();
|
|
||||||
result.push(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JX.$(config.input).value = JX.JSON.stringify(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
JX.DOM.listen(
|
|
||||||
root,
|
|
||||||
'click',
|
|
||||||
'add-resource',
|
|
||||||
onaddresource);
|
|
||||||
|
|
||||||
JX.DOM.listen(
|
|
||||||
root,
|
|
||||||
'submit',
|
|
||||||
null,
|
|
||||||
onsubmit);
|
|
||||||
|
|
||||||
manager.listen(
|
|
||||||
'row-removed',
|
|
||||||
function(row_id) {
|
|
||||||
delete resource_rows[row_id];
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in a new issue