taiga-front/app/coffee/modules/kanban/main.coffee

326 lines
11 KiB
CoffeeScript

###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
#
# 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 <http://www.gnu.org/licenses/>.
#
# File: modules/kanban/main.coffee
###
taiga = @.taiga
mixOf = @.taiga.mixOf
toggleText = @.taiga.toggleText
scopeDefer = @.taiga.scopeDefer
bindOnce = @.taiga.bindOnce
groupBy = @.taiga.groupBy
timeout = @.taiga.timeout
module = angular.module("taigaKanban")
#############################################################################
## Kanban Controller
#############################################################################
class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin)
@.$inject = [
"$scope",
"$rootScope",
"$tgRepo",
"$tgConfirm",
"$tgResources",
"$routeParams",
"$q",
"$tgLocation"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location) ->
_.bindAll(@)
@scope.sectionName = "Kanban"
promise = @.loadInitialData()
promise.then null, =>
console.log "FAIL"
@scope.$on("usform:new:success", @.onNewUserstory)
@scope.$on("usform:bulk:success", @.onNewUserstory)
@scope.$on("usform:edit:success", @.onUserstoryEdited)
@scope.$on("assigned-to:added", @.onAssignedToChanged)
@scope.$on("kanban:us:move", @.moveUs)
# Template actions
editUserStory: (us) ->
@rootscope.$broadcast("usform:edit", us)
addNewUs: (type, statusId) ->
switch type
when "standard" then @rootscope.$broadcast("usform:new", @scope.projectId, statusId)
when "bulk" then @rootscope.$broadcast("usform:bulk", @scope.projectId, statusId)
changeUsAssignedTo: (us) ->
@rootscope.$broadcast("assigned-to:add", us)
# Scope Events Handlers
onNewUserstory: (ctx, result) ->
if result.data
items = result.data
else
items = [result]
for us in items
@scope.usByStatus[us.status].splice(0, 0, us)
onAssignedToChanged: (ctx, userid, us) ->
us.assigned_to = userid
promise = @repo.save(us)
promise.then null, ->
console.log "FAIL" # TODO
onUserstoryEdited: (ctx, us) ->
@.loadUserstories()
# Load data methods
loadProjectStats: ->
return @rs.projects.stats(@scope.projectId).then (stats) =>
@scope.stats = stats
if stats.total_points
completedPercentage = Math.round(100 * stats.closed_points / stats.total_points)
else
completedPercentage = 0
@scope.stats.completedPercentage = "#{completedPercentage}%"
return stats
loadUserstories: ->
return @rs.userstories.listUnassigned(@scope.projectId).then (userstories) =>
@scope.userstories = userstories
@scope.usByStatus = _.groupBy(userstories, "status")
for status in @scope.usStatusList
if not @scope.usByStatus[status.id]?
@scope.usByStatus[status.id] = []
# The broadcast must be executed when the DOM has been fully reloaded.
# We can't assure when this exactly happens so we need a defer
scopeDefer @scope, =>
@scope.$broadcast("userstories:loaded")
return userstories
loadKanban: ->
return @q.all([
@.loadProjectStats(),
@.loadUserstories()
])
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project
@scope.$emit('project:loaded', project)
@scope.points = _.sortBy(project.points, "order")
@scope.pointsById = groupBy(project.points, (x) -> x.id)
@scope.usStatusById = groupBy(project.us_statuses, (x) -> x.id)
@scope.usStatusList = _.sortBy(project.us_statuses, "order")
return project
loadInitialData: ->
# Resolve project slug
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then(=> @.loadProject())
.then(=> @.loadUsersAndRoles())
.then(=> @.loadKanban())
.then(=> @scope.$broadcast("redraw:wip"))
prepareBulkUpdateData: (uses) ->
return _.map(uses, (x) -> [x.id, x.order])
resortUserStories: (uses) ->
items = []
for item, index in uses
item.order = index
if item.isModified()
items.push(item)
return items
moveUs: (ctx, us, statusId, index) ->
if us.status != statusId
# Remove us from old status column
r = @scope.usByStatus[us.status].indexOf(us)
@scope.usByStatus[us.status].splice(r, 1)
# Add us to new status column.
@scope.usByStatus[statusId].splice(index, 0, us)
us.status = statusId
# Persist the userstory
promise = @repo.save(us)
# Rehash userstories order field
# and persist in bulk all changes.
promise = promise.then =>
items = @.resortUserStories(@scope.usByStatus[statusId])
data = @.prepareBulkUpdateData(items)
return @rs.userstories.bulkUpdateOrder(us.project, data).then =>
# @rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId)
return items
promise.then null, ->
# TODO
console.log "FAIL"
return promise
module.controller("KanbanController", KanbanController)
#############################################################################
## Kanban Directive
#############################################################################
KanbanDirective = ($repo, $rootscope) ->
link = ($scope, $el, $attrs) ->
tableBodyDom = $el.find(".kanban-table-body")
tableBodyDom.on "scroll", (event) ->
target = angular.element(event.currentTarget)
tableHeaderDom = $el.find(".kanban-table-header .kanban-table-inner")
tableHeaderDom.css("left", -1 * target.scrollLeft())
return {link: link}
module.directive("tgKanban", ["$tgRepo", "$rootScope", KanbanDirective])
#############################################################################
## Kanban Row Size Fixer Directive
#############################################################################
KanbanRowSizeFixer = ->
link = ($scope, $el, $attrs) ->
bindOnce $scope, "usStatusList", (statuses) ->
itemSize = 310
size = (statuses.length * itemSize) - 10
$el.css("width", "#{size}px")
return {link: link}
module.directive("tgKanbanRowSizeFixer", KanbanRowSizeFixer)
#############################################################################
## Kaban User Story Directive
#############################################################################
KanbanUserstoryDirective = ->
link = ($scope, $el, $attrs) ->
$el.disableSelection()
return {
templateUrl: "/partials/views/components/kanban-task.html"
link: link
require: "ngModel"
}
module.directive("tgKanbanUserstory", KanbanUserstoryDirective)
#############################################################################
## Kaban WIP Limit Directive
#############################################################################
KanbanWipLimitDirective = ->
link = ($scope, $el, $attrs) ->
$el.disableSelection()
redrawWipLimit = ->
$el.find('.kanban-wip-limit').remove()
timeout 200, ->
element = $el.find('.kanban-task')[$scope.status.wip_limit]
if element
angular.element(element).before("<div class='kanban-wip-limit'></div>")
$scope.$on "redraw:wip", redrawWipLimit
$scope.$on "kanban:us:move", redrawWipLimit
$scope.$on "usform:new:success", redrawWipLimit
$scope.$on "usform:bulk:success", redrawWipLimit
return {link: link}
module.directive("tgKanbanWipLimit", KanbanWipLimitDirective)
#############################################################################
## Kanban User Directive
#############################################################################
KanbanUserDirective = ($log) ->
template = _.template("""
<figure class="avatar">
<a href="#" title="Change assignation" <% if (!clickable) {%>class="not-clickable"<% } %>>
<img src="<%= imgurl %>" alt="<%- name %>" class="avatar">
</a>
</figure>
""") # TODO: i18n
clickable = false
link = ($scope, $el, $attrs, $model) ->
if not $attrs.tgKanbanUserAvatar
return $log.error "KanbanUserDirective: no attr is defined"
wtid = $scope.$watch $attrs.tgKanbanUserAvatar, (v) ->
if not $scope.usersById?
$log.error "KanbanUserDirective requires userById set in scope."
wtid()
else
user = $scope.usersById[v]
render(user)
render = (user) ->
if user is undefined
ctx = {name: "Unassigned", imgurl: "/images/unnamed.png", clickable: clickable}
else
ctx = {name: user.full_name_display, imgurl: user.photo, clickable: clickable}
html = template(ctx)
$el.html(html)
$el.parent().find("a.task-assigned").html(ctx.name)
bindOnce $scope, "project", (project) ->
if project.my_permissions.indexOf("modify_us") > -1
clickable = true
$el.on "click", (event) =>
us = $model.$modelValue
$ctrl = $el.controller()
$ctrl.changeUsAssignedTo(us)
return {link: link, require:"ngModel"}
module.directive("tgKanbanUserAvatar", ["$log", KanbanUserDirective])