diff --git a/app/modules/components/assigned/assigned-to-inline.directive.coffee b/app/modules/components/assigned/assigned-to-inline.directive.coffee new file mode 100644 index 00000000..75559169 --- /dev/null +++ b/app/modules/components/assigned/assigned-to-inline.directive.coffee @@ -0,0 +1,101 @@ +### +# Copyright (C) 2014-2018 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: assigned-to-inline.directive.coffee +### + +AssignedToInlineDirective = ($rootscope, $confirm, $repo, $loading, $modelTransform, $template +$translate, $compile, $currentUserService, avatarService) -> + link = ($scope, $el, $attrs, $ctrl) -> + isEditable = -> + return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1 + + renderUserList = (text) -> + activeUsers = _.reject($scope.activeUsers, {"id": $scope.selected.id}) if $scope.selected? + users = $ctrl.getUserList(activeUsersactiveUsers, $scope.user.id, text) + + visibleUsers = _.slice(users, 0, 5) + visibleUsers = _.map visibleUsers, (user) -> user.avatar = avatarService.getAvatar(user) + + $scope.users = _.slice(users, 0, 5) + $scope.showMore = users.length > 5 + + renderUser = (assignedObject) -> + if assignedObject?.assigned_to + $scope.selected = assignedObject.assigned_to + assigned_to_extra_info = $scope.usersById[$scope.selected] + $scope.fullName = assigned_to_extra_info?.full_name_display + $scope.isUnassigned = false + $scope.avatar = avatarService.getAvatar(assigned_to_extra_info) + $scope.bg = $scope.avatar.bg + $scope.isIocaine = assignedObject?.is_iocaine + else + $scope.fullName = $translate.instant("COMMON.ASSIGNED_TO.ASSIGN") + $scope.isUnassigned = true + $scope.avatar = avatarService.getAvatar(null) + $scope.bg = null + $scope.isIocaine = false + + $scope.fullNameVisible = !($scope.isUnassigned && !$currentUserService.isAuthenticated()) + $scope.isEditable = isEditable() + + $el.on "click", ".users-search", (event) -> + event.stopPropagation() + + $el.on "click", ".users-dropdown", (event) -> + event.preventDefault() + event.stopPropagation() + renderUserList() + $scope.$apply() + $el.find(".pop-users").popover().open() + + $scope.selfAssign = () -> + $attr.ngModel.assigned_to = $currentUserService.getUser().get('id') + renderUser($attr.ngModel) + + $scope.unassign = () -> + $attr.ngModel.assigned_to = null + renderUser() + + $scope.$watch "usersSearch", (searchingText) -> + if searchingText? + renderUserList(searchingText) + $el.find('input').focus() + + $el.on "click", ".user-list-single", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + $attr.ngModel.assigned_to = target.data("user-id") + renderUser($attr.ngModel) + $scope.$apply() + + $scope.$watch $attrs.ngModel, (instance) -> + renderUser(instance) + + $scope.$on "isiocaine:changed", (ctx, instance) -> + renderUser(instance) + + $scope.$on "$destroy", -> + $el.off() + + return { + link:link, + templateUrl: "common/components/assigned-to-inline.html" + } + +angular.module('taigaComponents').directive("tgAssignedToInline", ["$rootScope", "$tgConfirm", +"$tgRepo", "$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$translate", "$compile", +"tgCurrentUserService", "tgAvatarService", AssignedToInlineDirective]) diff --git a/app/modules/components/assigned/assigned-to.directive.coffee b/app/modules/components/assigned/assigned-to.directive.coffee new file mode 100644 index 00000000..e7cd0157 --- /dev/null +++ b/app/modules/components/assigned/assigned-to.directive.coffee @@ -0,0 +1,105 @@ +### +# Copyright (C) 2014-2018 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: assigned-to.directive.coffee +### + +AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $modelTransform, $template, +$translate, $compile, $currentUserService, avatarService) -> + # You have to include a div with the tg-lb-assignedto directive in the page + # where use this directive + template = $template.get("common/components/assigned-to.html", true) + + link = ($scope, $el, $attrs, $model) -> + isEditable = -> + return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1 + + save = (userId) -> + item = $model.$modelValue.clone() + item.assigned_to = userId + + currentLoading = $loading() + .target($el) + .start() + + transform = $modelTransform.save (item) -> + item.assigned_to = userId + return item + + transform.then (item) -> + currentLoading.finish() + $rootscope.$broadcast("object:updated") + + transform.then null, -> + $confirm.notify("error") + currentLoading.finish() + + return transform + + render = () -> + template = $template.get("common/components/assigned-to.html") + templateScope = $scope.$new() + compiledTemplate = $compile(template)(templateScope) + $el.html(compiledTemplate) + + $scope.assign = () -> + $rootscope.$broadcast("assigned-to:add", $model.$modelValue) + + $scope.unassign = () -> + title = $translate.instant("COMMON.ASSIGNED_TO.CONFIRM_UNASSIGNED") + $confirm.ask(title).then (response) -> + response.finish() + save(null) + + $scope.selfAssign = () -> + userId = $currentUserService.getUser().get('id') + save(userId) + + $scope.$on "assigned-to:added", (ctx, userId, item) -> + return if item.id != $model.$modelValue.id + save(userId) + + $scope.$watch $attrs.ngModel, (instance) -> + if instance?.assigned_to + $scope.selected = instance.assigned_to + assigned_to_extra_info = $scope.usersById[$scope.selected] + $scope.fullName = assigned_to_extra_info?.full_name_display + $scope.isUnassigned = false + $scope.avatar = avatarService.getAvatar(assigned_to_extra_info) + $scope.bg = $scope.avatar.bg + $scope.isIocaine = instance?.is_iocaine + else + $scope.fullName = $translate.instant("COMMON.ASSIGNED_TO.ASSIGN") + $scope.isUnassigned = true + $scope.avatar = avatarService.getAvatar(null) + $scope.bg = null + $scope.isIocaine = false + + $scope.fullNameVisible = !($scope.isUnassigned && !$currentUserService.isAuthenticated()) + $scope.isEditable = isEditable() + render() + + $scope.$on "$destroy", -> + $el.off() + + return { + link:link, + require:"ngModel" + } + +angular.module('taigaComponents').directive("tgAssignedTo", ["$rootScope", "$tgConfirm", "$tgRepo", +"$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$translate", "$compile", +"tgCurrentUserService", "tgAvatarService", AssignedToDirective]) diff --git a/app/modules/components/assigned/assigned-users-inline.directive.coffee b/app/modules/components/assigned/assigned-users-inline.directive.coffee new file mode 100644 index 00000000..db762f66 --- /dev/null +++ b/app/modules/components/assigned/assigned-users-inline.directive.coffee @@ -0,0 +1,129 @@ +### +# Copyright (C) 2014-2018 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: assigned-users-inline.directive.coffee +### + +AssignedUsersInlineDirective = ($rootscope, $confirm, $repo, $loading, $modelTransform, $template +$translate, $compile, $currentUserService, avatarService, $userListService) -> + link = ($scope, $el, $attrs, $ctrl) -> + currentAssignedIds = [] + currentAssignedTo = null + + isAssigned = -> + return currentAssignedIds.length > 0 + + renderUsersList = (text) -> + users = $userListService.searchUsers(text) + + # Add selected users + selected = [] + _.map users, (user) -> + if user.id in currentAssignedIds + user.avatar = avatarService.getAvatar(user) + selected.push(user) + + # Filter users in searchs + visible = [] + _.map users, (user) -> + if user.id not in currentAssignedIds + user.avatar = avatarService.getAvatar(user) + visible.push(user) + + $scope.selected = _.slice(selected, 0, 5) + if $scope.selected.length < 5 + $scope.users = _.slice(visible, 0, 5 - $scope.selected.length) + else + $scope.users = [] + $scope.showMore = users.length > 5 + + renderUsers = () -> + assignedUsers = _.map(currentAssignedIds, (assignedUserId) -> $scope.usersById[assignedUserId]) + assignedUsers = _.filter assignedUsers, (it) -> return !!it + + $scope.hiddenUsers = if currentAssignedIds.length > 3 then currentAssignedIds.length - 3 else 0 + $scope.assignedUsers = _.slice(assignedUsers, 0, 3) + + $scope.isAssigned = isAssigned() + + applyToModel = () -> + _.map currentAssignedIds, (userId) -> + if !$scope.usersById[userId] + currentAssignedIds.splice(currentAssignedIds.indexOf(userId), 1) + if currentAssignedIds.length == 0 + currentAssignedTo = null + else if currentAssignedIds.indexOf(currentAssignedTo) == -1 || !currentAssignedTo + currentAssignedTo = currentAssignedIds[0] + $attr.ngModel.setAttr('assigned_users', currentAssignedIds) + $attr.ngModel.assigned_to = currentAssignedTo + + $el.on "click", ".users-dropdown", (event) -> + event.preventDefault() + event.stopPropagation() + renderUsersList() + $scope.$apply() + $el.find(".pop-users").popover().open() + + $scope.selfAssign = () -> + currentAssignedIds.push($currentUserService.getUser().get('id')) + renderUsers() + applyToModel() + $scope.usersSearch = null + + $el.on "click", ".users-search", (event) -> + event.stopPropagation() + + $scope.$watch "usersSearch", (searchingText) -> + if searchingText? + renderUsersList(searchingText) + $el.find('input').focus() + + $el.on "click", ".user-list-single", (event) -> + event.preventDefault() + event.stopPropagation() + target = angular.element(event.currentTarget) + index = currentAssignedIds.indexOf(target.data("user-id")) + if index == -1 + currentAssignedIds.push(target.data("user-id")) + else + currentAssignedIds.splice(index, 1) + renderUsers() + $el.find(".pop-users").popover().close() + $scope.usersSearch = null + $scope.$apply() + + $scope.$watch $attrs.ngModel, (item) -> + return if not item? + currentAssignedIds = [] + assigned_to = null + + if item.assigned_users? + currentAssignedIds = item.assigned_users + assigned_to = item.assigned_to + renderUsers() + + $scope.$on "$destroy", -> + $el.off() + + return { + scope: true, + link:link, + templateUrl: "common/components/assigned-users-inline.html" + } + +angular.module('taigaComponents').directive("tgAssignedUsersInline", ["$rootScope", "$tgConfirm", +"$tgRepo", "$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$translate", "$compile", +"tgCurrentUserService", "tgAvatarService", "tgUserListService", AssignedUsersInlineDirective]) diff --git a/app/modules/components/assigned/assigned-users.directive.coffee b/app/modules/components/assigned/assigned-users.directive.coffee new file mode 100644 index 00000000..c6ddc954 --- /dev/null +++ b/app/modules/components/assigned/assigned-users.directive.coffee @@ -0,0 +1,135 @@ +### +# Copyright (C) 2014-2018 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: assigned-users.directive.coffee +### + +AssignedUsersDirective = ($rootscope, $confirm, $repo, $modelTransform, $template, $compile, +$translate, $currentUserService) -> + # You have to include a div with the tg-lb-assignedusers directive in the page + # where use this directive + + link = ($scope, $el, $attrs, $model) -> + isEditable = -> + return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1 + isAssigned = -> + return $scope.assignedUsers.length > 0 + + save = (assignedUsers, assignedToUser) -> + transform = $modelTransform.save (item) -> + item.assigned_users = assignedUsers + if not item.assigned_to + item.assigned_to = assignedToUser + return item + + transform.then -> + assignedUsers = _.map(assignedUsers, (assignedUserId) -> $scope.usersById[assignedUserId]) + renderAssignedUsers(assignedUsers) + result = $rootscope.$broadcast("object:updated") + + transform.then null, -> + $confirm.notify("error") + + openAssignedUsers = -> + item = _.clone($model.$modelValue, false) + $rootscope.$broadcast("assigned-user:add", item) + + $scope.selfAssign = () -> + return if not isEditable() + currentUserId = $currentUserService.getUser().get('id') + assignedUsers = _.clone($model.$modelValue.assigned_users, false) + assignedUsers.push(currentUserId) + assignedUsers = _.uniq(assignedUsers) + save(assignedUsers, currentUserId) + + $scope.unassign = (user) -> + return if not isEditable() + target = angular.element(event.currentTarget) + assignedUserId = user.id + + title = $translate.instant("COMMON.ASSIGNED_USERS.TITLE_LIGHTBOX_DELETE_ASSIGNED") + message = $scope.usersById[assignedUserId].full_name_display + + $confirm.askOnDelete(title, message).then (askResponse) -> + askResponse.finish() + + assignedUserIds = _.clone($model.$modelValue.assigned_users, false) + assignedUserIds = _.pull(assignedUserIds, assignedUserId) + + deleteAssignedUser(assignedUserIds) + + deleteAssignedUser = (assignedUserIds) -> + transform = $modelTransform.save (item) -> + item.assigned_users = assignedUserIds + + # Update as + if item.assigned_to not in assignedUserIds and assignedUserIds.length > 0 + item.assigned_to = assignedUserIds[0] + if assignedUserIds.length == 0 + item.assigned_to = null + + return item + + transform.then () -> + item = $modelTransform.getObj() + assignedUsers = _.map(item.assignedUsers, (assignedUserId) -> $scope.usersById[assignedUserId]) + renderAssignedUsers(assignedUsers) + $rootscope.$broadcast("object:updated") + + transform.then null, -> + item.revert() + $confirm.notify("error") + + renderAssignedUsers = (assignedUsers) -> + $scope.assignedUsers = assignedUsers + $scope.isEditable = isEditable() + $scope.isAssigned = isAssigned() + $scope.openAssignedUsers = openAssignedUsers + + $scope.$on "assigned-user:deleted", (ctx, assignedUserId) -> + assignedUsersIds = _.clone($model.$modelValue.assigned_users, false) + assignedUsersIds = _.pull(assignedUsersIds, assignedUserId) + assignedUsersIds = _.uniq(assignedUsersIds) + deleteAssignedUser(assignedUsersIds) + + $scope.$on "assigned-user:added", (ctx, assignedUserId) -> + assignedUsers = _.clone($model.$modelValue.assigned_users, false) + assignedUsers.push(assignedUserId) + assignedUsers = _.uniq(assignedUsers) + + # Save assigned_users and assignedUserId for assign_to legacy attribute + save(assignedUsers, assignedUserId) + + $scope.$watch $attrs.ngModel, (item) -> + return if not item? + assignedUsers = _.map(item.assigned_users, (assignedUserId) -> $scope.usersById[assignedUserId]) + assignedUsers = _.filter assignedUsers, (it) -> return !!it + + renderAssignedUsers(assignedUsers) + + $scope.$on "$destroy", -> + $el.off() + + return { + scope: true, + templateUrl: "common/components/assigned-users.html", + link:link + require:"ngModel" + } + +angular.module('taigaComponents').directive("tgAssignedUsers", ["$rootScope", "$tgConfirm", +"$tgRepo", "$tgQueueModelTransformation", "$tgTemplate", "$compile", "$translate", +"tgCurrentUserService", AssignedUsersDirective]) diff --git a/app/modules/services/user-list.service.coffee b/app/modules/services/user-list.service.coffee new file mode 100644 index 00000000..688822ef --- /dev/null +++ b/app/modules/services/user-list.service.coffee @@ -0,0 +1,47 @@ +### +# Copyright (C) 2014-2017 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: user-list.service.coffee +### + +taiga = @.taiga +normalizeString = @.taiga.normalizeString + +class UserListService + @.$inject = [ + "tgCurrentUserService" + "tgProjectService" + ] + + constructor: (@currentUserService, @projectService) -> + @.currentUser = @currentUserService.getUser().toJS() + @.members = @projectService.project.toJS().members + + filterUsers: (text, user) -> + username = user.full_name_display.toUpperCase() + username = normalizeString(username) + text = text.toUpperCase() + text = normalizeString(text) + return _.includes(username, text) + + searchUsers: (text, excludedUser) -> + users = _.clone(@.members, true) + users = _.reject(users, {"id": excludedUser.id}) if excludedUser + users = _.sortBy(users, (o) => if o.id is @.currentUser.id then 0 else o.id) + users = _.filter(users, _.partial(@.filterUsers, text)) if text? + return users + +angular.module("taigaCommon").service("tgUserListService", UserListService)