[#369] project collaborators page: in progress

This commit is contained in:
Alexander Machehin 2014-11-15 01:31:45 +05:00
parent 6dc75e264d
commit 2a5ec70a6b
17 changed files with 184 additions and 191 deletions

View File

@ -47,7 +47,7 @@ gem 'wikicloth'
gem 'newrelic_rpm'
gem 'whenever', '~> 0.9.0', require: false
gem 'jbuilder', '~> 2.1'
gem 'jbuilder', '~> 2.2'
gem 'rails3-jquery-autocomplete'
gem 'will_paginate', '~> 3.0'
gem 'meta-tags', '~> 2.0', require: 'meta_tags'

View File

@ -213,7 +213,7 @@ GEM
inherited_resources (1.4.1)
has_scope (~> 0.6.0.rc)
responders (~> 1.0.0.rc)
jbuilder (2.1.3)
jbuilder (2.2.4)
activesupport (>= 3.0.0, < 5)
multi_json (~> 1.2)
jquery-migrate-rails (1.2.1)
@ -563,7 +563,7 @@ DEPENDENCIES
haml-rails (~> 0.5)
highline (~> 1.6.20)
hirb
jbuilder (~> 2.1)
jbuilder (~> 2.2)
jquery-migrate-rails
jquery-rails (~> 2.3)
js-routes

View File

@ -0,0 +1,39 @@
CollaboratorsController = (dataservice, Collaborator, $http) ->
vm = this
vm.new_role = 'reader'
vm.getCollaborators = (val) ->
return [] if val.length <= 2
Collaborator.find(vm.name_with_owner, val)
vm.selectCollaborator = (item, model, label) ->
vm.selected_new_collaborator = item
false
vm.addCollaborator = ->
promise = Collaborator.add(vm.name_with_owner,
vm.selected_new_collaborator,
vm.new_role,
vm.project_id)
promise.success (data) ->
vm.collaborators.push data
vm.selected_new_collaborator = null
false
init = (dataservice) ->
vm.name_with_owner = dataservice.name_with_owner
vm.project_id = dataservice.project_id
vm.collaborators = dataservice.collaborators
init(dataservice)
return true
angular
.module("RosaABF")
.controller "CollaboratorsController", CollaboratorsController
CollaboratorsController.$inject = ['CollaboratorsInitializer', 'Collaborator', '$http']

View File

@ -1,27 +0,0 @@
RosaABF.factory('ApiCollaborator', ['$resource', function($resource) {
var CollaboratorResource = $resource(
'/:owner/:project/collaborators/:id?format=json',
{
owner: '@project.owner_uname',
project: '@project.name',
id: '@id'
},
{
update: {
method: 'PUT',
isArray : false
},
find: {
url: '/:owner/:project/collaborators/find',
format: 'json',
method: 'GET',
isArray : true
}
}
);
return {
resource : CollaboratorResource
}
}]);

View File

@ -0,0 +1,34 @@
collaboratorService = ($http) ->
{
find: (name_with_owner, val) ->
path = Routes.find_project_collaborators_path(
{
name_with_owner: name_with_owner,
term: val
}
)
$http.get(path).then (response) ->
response.data
add: (name_with_owner, selected, role, project_id) ->
path = Routes.project_collaborators_path(
{
name_with_owner: name_with_owner,
collaborator: {
actor_id: selected.actor_id
actor_type: selected.actor_type
role: role
project_id: project_id
}
}
)
$http.post(path)
}
angular
.module("RosaABF")
.factory "Collaborator", collaboratorService
collaboratorService.$inject = ['$http']

View File

@ -1,65 +0,0 @@
RosaABF.controller('CollaboratorsController', ['$scope', 'ApiCollaborator', function($scope, ApiCollaborator) {
$scope.def_role = "<%=Relation::ROLES.first%>";
$scope.popup = $('#add_collaborator_form .users-search-popup');
$scope.owner = $('#owner_name').val();
$scope.project = $('#project_name').val();
$scope.resource = ApiCollaborator.resource;
$scope.collaborators = [];
$scope.new_collaborators = [];
$scope.initNewCollaborator = function(c) {
if (c) {
c.term = c.actor_name;
c.collaborator.role = $scope.def_role;
} else {
c = {collaborator: {role: $scope.def_role}};
}
$scope.new_collaborator = c;
}
$scope.initNewCollaborator();
$scope.getCollaborators = function() {
$scope.collaborators = $scope.resource.query({owner: $scope.owner, project: $scope.project});
}
$scope.getCollaborators();
$scope.update = function(collaborator) {
collaborator.$update();
}
$scope.deleteCollaborators = function() {
var collaborators = [];
_.each($scope.collaborators, function(collaborator){
if(collaborator.removed) {
collaborator.$delete();
} else {
collaborators.push(collaborator);
}
$scope.collaborators = collaborators;
});
}
$scope.search = function() {
if ($scope.new_collaborator.term.length > 2) {
$scope.new_collaborators = $scope.resource.find(
{owner: $scope.owner, project: $scope.project, term: $scope.new_collaborator.term});
$scope.popup.show();
}
}
$scope.select = function(c) {
$scope.initNewCollaborator(c);
$scope.popup.hide();
}
$scope.add = function() {
$scope.new_collaborator.$save(function() {
$scope.collaborators.push($scope.new_collaborator);
$scope.initNewCollaborator();
});
}
}]);

View File

@ -17,24 +17,22 @@ class Projects::CollaboratorsController < Projects::BaseController
users = User.not_member_of(@project)
groups = Group.not_member_of(@project)
if params[:term].present?
users = users.search(params[:term])
groups = groups.search(params[:term])
users = users.search(params[:term]).first(5)
groups = groups.search(params[:term]).first(5)
end
@collaborators = (users | groups).map{|act| Collaborator.new(actor: act, project: @project)}
respond_with @collaborators do |format|
format.json { render 'index' }
end
respond_with @collaborators
end
def create
@collaborator = Collaborator.new(params[:collaborator])
@collaborator.project = @project
if @collaborator.save
respond_with @collaborator do |format|
respond_to do |format|
if @collaborator.save
format.json { render partial: 'collaborator', locals: {collaborator: @collaborator} }
else
format.json { render text: 'error', status: 422 }
end
else
raise
end
end

View File

@ -87,6 +87,10 @@ class Collaborator
end
end
def actor_uname
@actor.uname
end
def project_id
@project.try(:id)
end

View File

@ -1,9 +1,4 @@
json.(collaborator, :id, :actor_name)
json.collaborator do # attr_accessible for AngularJS
json.(collaborator, :role, :actor_id, :actor_type, :project_id)
end
json.project do
json.(collaborator.project, :name, :owner_uname)
end
json.avatar avatar_url(collaborator.actor)
json.actor_path participant_path(collaborator.actor)
json.(collaborator, :id, :actor_name, :actor_type, :actor_id, :role)
json.avatar avatar_url(collaborator.actor)
json.path participant_path(collaborator.actor)

View File

@ -1,3 +1,3 @@
json.array!(collaborators) do |collaborator|
json.partial! 'projects/collaborators/collaborator', collaborator: collaborator
json.partial! 'collaborator.json', collaborator: collaborator
end

View File

@ -0,0 +1,9 @@
<script>
angular.module('RosaABF').service('CollaboratorsInitializer', function(){
return {
name_with_owner: '<%= @project.name_with_owner %>',
project_id: <%= @project.id %>,
collaborators: <%= render('collaborators.json', collaborators: @collaborators).html_safe %>
};
});
</script>

View File

@ -0,0 +1,66 @@
table.table.table-striped
thead
tr
th
th
= t('layout.collaborators.members')
th.buttons.text-center colspan=3
= t('layout.collaborators.roles')
th.buttons
= t('layout.remove')
tbody
tr ng-repeat = 'member in membersCtrl.collaborators'
td
= check_box_tag 'members[]', 'true', false,
'ng-model' => 'member.check_delete',
'ng-value' => 'member.check_delete'
td
span
img ng-src = '{{ member.avatar_path }}' size = '30x30'
| &nbsp;
a[ ng-href = 'member.path' ] {{ member.actor_name }}
- Relation::ROLES.each do |role|
td
input[
type = 'radio'
ng-model = 'member.role'
value = role ]
| &nbsp;
= t("layout.collaborators.role_names.#{ role }")
td
a[ ng-click = 'membersCtrl.updateCollaborator(member)'
ng-confirm-click = t('layout.confirm') ]
span.glyphicon.glyphicon-ok
| &nbsp;
a[ ng-click = 'membersCtrl.removeCollaborator(member)'
ng-confirm-click = t('layout.confirm') ]
span.glyphicon.glyphicon-remove
| &nbsp;
= submit_tag t('layout.delete'), class: 'btn btn-danger',
'ng-click' => 'membersCtrl.RemoveCollaborators()'
hr
= simple_form_for :user, url: '',
html: { class: 'form-inline' },
wrapper: :inline_form do |f|
=> f.input :uname,
input_html: {'ng-model' => 'membersCtrl.selected_new_collaborator.actor_uname',
'typeahead' => 'member.actor_uname for member in membersCtrl.getCollaborators($viewValue)',
'typeahead-on-select' => 'membersCtrl.selectCollaborator($item, $model, $label)' }
=> f.input :role,
collection: options_for_collaborators_roles_select,
input_html: { name: :role, 'ng-model' => 'membersCtrl.new_role' },
include_blank: false
= f.button :submit, t('layout.add'), 'ng-click' => 'membersCtrl.addCollaborator()',
'ng-disabled' => '!membersCtrl.selected_new_collaborator'
- content_for :additional_scripts do
= render 'init_service.js.erb'

View File

@ -0,0 +1,3 @@
json.array!(@collaborators) do |collaborator|
json.(collaborator, :actor_uname, :actor_id, :actor_type)
end

View File

@ -1,74 +0,0 @@
-set_meta_tags title: [title_object(@project), t('layout.projects.members')]
= render 'sidebar'
= render 'submenu'
%a{name: 'users'}
%h3= t("layout.users.list_header")
#collaborators{'ng-controller' => 'CollaboratorsController'}
= hidden_field_tag :owner_name, @project.try(:owner).try(:uname)
= hidden_field_tag :project_name, @project.try(:name)
#add_collaborator_form
.admin-search.withimage
.img
%img{alt: 'avatar', 'ng-src' => '{{new_collaborator.avatar}}', 'ng-show' => 'new_collaborator.avatar'}
= text_field_tag :collaborator_name, nil, 'ng-model' => 'new_collaborator.term', 'ng-keyup' => 'search()'
.admin-role
.lineForm
= select_tag 'role', options_for_collaborators_roles_select, 'ng-model' => 'new_collaborator.collaborator.role'
.admin-add
%a.button{rel: 'nofollow', href: '', 'ng-click' => 'add()'}
= t('layout.add')
.both
.users-search-popup
.header
.title= t('layout.issues.search_user')
%span.icon-remove-circle
.list
.people{'ng-repeat' => 'c in new_collaborators', 'ng-click' => 'select(c)'}
.avatar
%img{width: '16px', 'ng-src' => '{{c.avatar}}', alt: 'avatar'}
.name {{c.actor_name}}
.both
.nothing{'ng-hide' => 'new_collaborators.length > 0'}= t('layout.issues.nothing_to_show')
.both
.both
%table.tablesorter{cellpadding: "0", cellspacing: "0"}
%thead
%tr
%th.centered
%span.delete{'ng-click' => 'deleteCollaborators()'} &nbsp; 
%th
= t("layout.collaborators.members")
%th{colspan: "3"}
= t("layout.collaborators.roles")
%tr.search
%th{colspan: "5"}
%input{type: "text", placeholder: "#{t('layout.filter_by_name')}", 'ng-model' => 'query.actor_name'}
%tbody
%tr{'ng-repeat' => 'c in collaborators | filter:query'}
%td
%input{type: 'checkbox', 'ng-model' => 'c.removed' }
%td
.img
%img{'ng-src' => '{{c.avatar}}', alt: 'avatar' }
.forimg
%a{'ng-href' => '{{c.actor_path}}'} {{c.actor_name}}
- Relation::ROLES.each do |role|
%td
.radio
%input{type: 'radio', 'ng-model' => 'c.collaborator.role', value: role, 'ng-click' => 'update(c)'}
.forradio
%label= t("layout.collaborators.role_names.#{role}")
%br
.both

View File

@ -0,0 +1,10 @@
-set_meta_tags title: [title_object(@project), t('layout.projects.members')]
= render 'submenu'
.container
.row
.col-md-offset-2.col-md-8= render 'settings_menu'
.col-md-9.col-md-offset-2[ ng-controller = 'CollaboratorsController as membersCtrl'
ng-cloak = 'true' ]
= render 'members_table'

View File

@ -1 +1 @@
json.partial! 'collaborators', collaborators: @collaborators
json.partial! 'collaborators.json', collaborators: @collaborators

View File

@ -15,7 +15,7 @@
= t("layout.remove")
tbody
- if update_roles_path
- actors = editable_object.actors.where(actor_id: members.map(&:id), actor_type: 'User').to_a
- actors ||= editable_object.actors
- members.each do |user|
tr
- if can? :remove_members, editable_object
@ -63,8 +63,9 @@
wrapper: :inline_form do |f|
= hidden_field_tag 'member_id', nil, id: 'member_id_field'
- autocomplete_path ||= autocomplete_user_uname_autocompletes_path
= f.input :uname,
input_html: { 'data-ajax' => autocomplete_user_uname_autocompletes_path,
input_html: { 'data-ajax' => autocomplete_path,
'data-id' => '#member_id_field',
class: 'typeahead' }