fold/unfold taskboard tasks columns & us rows

stable
Juanfran 2014-11-18 17:01:12 +01:00 committed by David Barragán Merino
parent 1619ef9383
commit 533395202b
6 changed files with 223 additions and 161 deletions

View File

@ -27,6 +27,8 @@ generateHash = taiga.generateHash
resourceProvider = ($repo, $http, $urls, $storage) ->
service = {}
hashSuffix = "tasks-queryparams"
hashSuffixStatusColumnModes = "tasks-statuscolumnmodels"
hashSuffixUsRowModes = "tasks-usrowmodels"
service.get = (projectId, taskId) ->
params = service.getQueryParams(projectId)
@ -65,6 +67,28 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
hash = generateHash([projectId, ns])
return $storage.get(hash) or {}
service.storeStatusColumnModes = (projectId, params) ->
ns = "#{projectId}:#{hashSuffixStatusColumnModes}"
hash = generateHash([projectId, ns])
$storage.set(hash, params)
service.getStatusColumnModes = (projectId) ->
ns = "#{projectId}:#{hashSuffixStatusColumnModes}"
hash = generateHash([projectId, ns])
return $storage.get(hash) or {}
service.storeUsRowModes = (projectId, sprintId, params) ->
ns = "#{projectId}:#{hashSuffixUsRowModes}"
hash = generateHash([projectId, sprintId, ns])
$storage.set(hash, params)
service.getUsRowModes = (projectId, sprintId) ->
ns = "#{projectId}:#{hashSuffixUsRowModes}"
hash = generateHash([projectId, sprintId, ns])
return $storage.get(hash) or {}
return (instance) ->
instance.tasks = service

View File

@ -103,7 +103,6 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project
@scope.$emit('project:loaded', project)
# Not used at this momment
@scope.pointsList = _.sortBy(project.points, "order")
# @scope.roleList = _.sortBy(project.roles, "order")
@ -112,6 +111,9 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.taskStatusList = _.sortBy(project.task_statuses, "order")
@scope.usStatusList = _.sortBy(project.us_statuses, "order")
@scope.usStatusById = groupBy(project.us_statuses, (e) -> e.id)
@scope.$emit('project:loaded', project)
return project
loadSprintStats: ->
@ -218,6 +220,8 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
promise = @repo.save(task)
@rootscope.$broadcast("sprint:task:moved", task)
promise.then =>
@.refreshTasksOrder(tasks)
@.loadSprintStats()
@ -291,22 +295,6 @@ TaskboardTaskDirective = ($rootscope) ->
module.directive("tgTaskboardTask", ["$rootScope", TaskboardTaskDirective])
#############################################################################
## Taskboard Task Row Size Fixer Directive
#############################################################################
TaskboardRowWidthFixerDirective = ->
link = ($scope, $el, $attrs) ->
bindOnce $scope, "taskStatusList", (statuses) ->
itemSize = 300 + (10 * statuses.length)
size = (1 + statuses.length) * itemSize
$el.css("width", "#{size}px")
return {link: link}
module.directive("tgTaskboardRowWidthFixer", TaskboardRowWidthFixerDirective)
#############################################################################
## Taskboard Table Height Fixer Directive
#############################################################################
@ -331,64 +319,156 @@ TaskboardTableHeightFixerDirective = ->
module.directive("tgTaskboardTableHeightFixer", TaskboardTableHeightFixerDirective)
#############################################################################
## Taskboard Squish Column Directive
#############################################################################
TaskboardSquishColumnDirective = (rs) ->
avatarWidth = 40
link = ($scope, $el, $attrs) ->
$scope.$on "sprint:task:moved", () =>
recalculateTaskboardWidth()
bindOnce $scope, "usTasks", (project) ->
$scope.statusesFolded = rs.tasks.getStatusColumnModes($scope.project.id)
$scope.usFolded = rs.tasks.getUsRowModes($scope.project.id, $scope.sprintId)
recalculateTaskboardWidth()
$scope.foldStatus = (status) ->
$scope.statusesFolded[status.id] = !!!$scope.statusesFolded[status.id]
rs.tasks.storeStatusColumnModes($scope.projectId, $scope.statusesFolded)
recalculateTaskboardWidth()
$scope.foldUs = (us) ->
if !us
$scope.usFolded["unassigned"] = !!!$scope.usFolded["unassigned"]
else
$scope.usFolded[us.id] = !!!$scope.usFolded[us.id]
rs.tasks.storeUsRowModes($scope.projectId, $scope.sprintId, $scope.usFolded)
recalculateTaskboardWidth()
getCeilWidth = (usId, statusId) =>
tasks = $scope.usTasks[usId][statusId].length
if $scope.statusesFolded[statusId]
if tasks and $scope.usFolded[usId]
tasksMatrixSize = Math.round(Math.sqrt(tasks))
width = avatarWidth * tasksMatrixSize
else
width = avatarWidth
return width
return 0
setStatusColumnWidth = (statusId, width) =>
column = $el.find(".squish-status-#{statusId}")
if width
column.css('max-width', width)
else
column.removeAttr("style")
refreshTaskboardTableWidth = () =>
columnWidths = []
columns = $el.find(".task-colum-name")
columnWidths = _.map columns, (column) ->
return $(column).outerWidth(true)
totalWidth = _.reduce columnWidths, (total, width) ->
return total + width
$el.find('.taskboard-table-inner').css("width", totalWidth)
recalculateStatusColumnWidth = (statusId) =>
statusFoldedWidth = 0
_.forEach $scope.userstories, (us) ->
width = getCeilWidth(us.id, statusId)
statusFoldedWidth = width if width > statusFoldedWidth
setStatusColumnWidth(statusId, statusFoldedWidth)
recalculateTaskboardWidth = () =>
_.forEach $scope.taskStatusList, (status) ->
recalculateStatusColumnWidth(status.id)
refreshTaskboardTableWidth()
return
return {link: link}
module.directive("tgTaskboardSquishColumn", ["$tgResources", TaskboardSquishColumnDirective])
#############################################################################
## Taskboard User Directive
#############################################################################
TaskboardUserDirective = ($log) ->
template = _.template("""
<figure class="avatar">
<a href="#" title="Assign task" <% if (!clickable) {%>class="not-clickable"<% } %>>
<img src="<%- imgurl %>" alt="<%- name %>">
template = """
<figure class="avatar avatar-assigned-to">
<a href="#" title="Assign task" ng-class="{'not-clickable': !clickable}">
<img ng-src="{{imgurl}}">
</a>
</figure>
""") # TODO: i18n
<figure class="avatar avatar-task-link">
<a tg-nav="project-tasks-detail:project=project.slug,ref=task.ref" ng-attr-title="{{task.subject}}">
<img ng-src="{{imgurl}}">
</a>
</figure>
""" # TODO: i18n
clickable = false
link = ($scope, $el, $attrs, $model) ->
if not $attrs.tgTaskboardUserAvatar?
return $log.error "TaskboardUserDirective: no attr is defined"
link = ($scope, $el, $attrs) ->
username_label = $el.parent().find("a.task-assigned")
username_label.on "click", (event) ->
if $el.find('a').hasClass('noclick')
return
wtid = $scope.$watch $attrs.tgTaskboardUserAvatar, (v) ->
if not $scope.usersById?
$log.error "TaskboardUserDirective requires userById set in scope."
wtid()
else
user = $scope.usersById[v]
render(user)
$ctrl = $el.controller()
$ctrl.editTaskAssignedTo($scope.task)
$scope.$watch 'task.assigned_to', (assigned_to) ->
user = $scope.usersById[assigned_to]
render = (user) ->
if user is undefined
ctx = {name: "Unassigned", imgurl: "/images/unnamed.png", clickable: clickable}
_.assign($scope, {name: "Unassigned", imgurl: "/images/unnamed.png", clickable: clickable})
else
ctx = {name: user.full_name_display, imgurl: user.photo, clickable: clickable}
_.assign($scope, {name: user.full_name_display, imgurl: user.photo, clickable: clickable})
html = template(ctx)
$el.html(html)
username_label = $el.parent().find("a.task-assigned")
username_label.html(ctx.name)
username_label.on "click", (event) ->
if $el.find('a').hasClass('noclick')
return
username_label.text($scope.name)
us = $model.$modelValue
$ctrl = $el.controller()
$ctrl.editTaskAssignedTo(us)
bindOnce $scope, "project", (project) ->
if project.my_permissions.indexOf("modify_task") > -1
clickable = true
$el.on "click", (event) =>
$el.find(".avatar-assigned-to").on "click", (event) =>
if $el.find('a').hasClass('noclick')
return
us = $model.$modelValue
$ctrl = $el.controller()
$ctrl.editTaskAssignedTo(us)
$ctrl.editTaskAssignedTo($scope.task)
return {link: link, require:"ngModel"}
return {
link: link,
template: template,
scope: {
"usersById": "=users",
"project": "=",
"task": "=",
}
}
module.directive("tgTaskboardUserAvatar", ["$log", TaskboardUserDirective])

View File

@ -1,7 +1,6 @@
div.taskboard-tagline(tg-colorize-tags="task.tags", tg-colorize-tags-type="taskboard")
div.taskboard-task-inner
div.taskboard-user-avatar(tg-taskboard-user-avatar="task.assigned_to", ng-model="task",
ng-class="{iocaine: task.is_iocaine}")
div.taskboard-user-avatar(tg-taskboard-user-avatar, users="usersById", task="task", project="project", ng-class="{iocaine: task.is_iocaine}")
span.icon.icon-iocaine(ng-if="task.is_iocaine", title="Feeling a bit overwhelmed by a task? Make sure others know about it by clicking on Iocaine when editing a task. It's possible to become immune to this (fictional) deadly poison by consuming small amounts over time just as it's possible to get better at what you do by occasionally taking on extra challenges!")
p.taskboard-text
a.task-assigned(href="", title="Assign task")

View File

@ -1,37 +1,18 @@
//
Column fold integration:
1. If click on column fold button search all TODOS and add class .colum-fold fold where indicated in comments.
2. Hide column fold button and show column unfold button (sibling)
3. Recalculate container table width.
Row Fold integration:
1. If click on row fold button search all TODOS and add class .row-fold fold where indicated in comments.
2. Hide row fold button and show column unfold button (sibling)
3. Recalculate container table width.
Row and column integration
1. I think we will need to do som JS calcs to fix this. Not sure at the moment.
div.taskboard-table
div.taskboard-table(tg-taskboard-squish-column)
div.taskboard-table-header
div.taskboard-table-inner(tg-taskboard-row-width-fixer)
div.taskboard-table-inner
h2.task-colum-name "User story"
//- TODO Add class .colum-fold in .task-column
h2.task-colum-name(ng-repeat="s in taskStatusList track by s.id",
ng-style="{'border-top-color':s.color}", tg-bo-title="s.name")
h2.task-colum-name(ng-repeat="s in taskStatusList track by s.id", ng-style="{'border-top-color':s.color}", ng-class="{'column-fold':statusesFolded[s.id]}", class="squish-status-{{s.id}}", tg-bo-title="s.name")
span(tg-bo-bind="s.name")
a.icon.icon-vfold.hfold(href="", title="Fold Column")
a.icon.icon-vunfold.hunfold.hidden(href="", title="Unfold Column")
a.icon.icon-vfold.hfold(href="", ng-click='foldStatus(s)', title="Fold Column", ng-class='{hidden:statusesFolded[s.id]}')
a.icon.icon-vunfold.hunfold(href="", title="Unfold Column", ng-click='foldStatus(s)', ng-class='{hidden:!statusesFolded[s.id]}')
div.taskboard-table-body(tg-taskboard-table-height-fixer)
div.taskboard-table-inner(tg-taskboard-row-width-fixer)
//- TODO: Add class .row-fold in .task-column
div.task-row(ng-repeat="us in userstories track by us.id", ng-class="{blocked: us.is_blocked}")
div.taskboard-table-inner
div.task-row(ng-repeat="us in userstories track by us.id", ng-class="{blocked: us.is_blocked, 'row-fold':usFolded[us.id]}")
div.taskboard-userstory-box.task-column(tg-bo-title="us.blocked_note")
a.icon.icon-vfold.vfold(href="", title="Fold Row")
a.icon.icon-vunfold.vunfold.hidden(href="", title="Unfold Row")
a.icon.icon-vfold.vfold(href="", title="Fold Row", ng-click='foldUs(us)', ng-class='{hidden:usFolded[us.id]}')
a.icon.icon-vunfold.vunfold(href="", title="Unfold Row", ng-click='foldUs(us)', ng-class='{hidden:!usFolded[us.id]}')
h3.us-title
a(href="", tg-nav="project-userstories-detail:project=project.slug,ref=us.ref",
tg-bo-title="'#' + us.ref + ' ' + us.subject")
@ -41,22 +22,19 @@ div.taskboard-table
span(ng-bind="us.total_points")
span points
include ../components/addnewtask
//- TODO: Add class .colum-fold in .task-column
div.taskboard-tasks-box.task-column(ng-repeat="st in taskStatusList track by st.id",
tg-taskboard-sortable)
div.taskboard-tasks-box.task-column(ng-repeat="st in taskStatusList track by st.id", tg-taskboard-sortable, class="squish-status-{{st.id}}", ng-class="{'column-fold':statusesFolded[st.id]}")
div.taskboard-task(ng-repeat="task in usTasks[us.id][st.id] track by task.id",
tg-taskboard-task)
include ../components/taskboard-task
//- TODO: Add class .row-fold in .task-Column
div.task-row(ng-init="us = null")
div.task-row(ng-init="us = null", ng-class="{'row-fold':usFolded['unassigned']}")
div.taskboard-userstory-box.task-column
a.icon.icon-vfold.vfold(href="", title="Fold Row", ng-click='foldUs()', ng-class="{hidden:usFolded['unassigned']}")
a.icon.icon-vunfold.vunfold(href="", title="Unfold Row", ng-click='foldUs()', ng-class="{hidden:!usFolded['unassigned']}")
h3.us-title
span Unassigned tasks
include ../components/addnewtask.jade
//- TODO Add class .colum-fold in .task-column
div.taskboard-tasks-box.task-column(ng-repeat="st in taskStatusList track by st.id",
tg-taskboard-sortable)
div.taskboard-tasks-box.task-column(ng-repeat="st in taskStatusList track by st.id", tg-taskboard-sortable, class="squish-status-{{st.id}}", ng-class="{'column-fold':statusesFolded[st.id]}")
div.taskboard-task(ng-repeat="task in usTasks[null][st.id] track by task.id",
tg-taskboard-task)
include ../components/taskboard-task

View File

@ -1,5 +1,4 @@
.taskboard-task {
@include transition (all .4s linear);
background: $postit;
border: 1px solid $postit-hover;
box-shadow: none;

View File

@ -1,11 +1,46 @@
//Table basic shared vars
$column-width: 300px;
$column-folded-width: 45px;
$column-flex: 1;
$column-flex: 0;
$column-shrink: 0;
$column-margin: 0 10px 0 0;
%fold {
.taskboard-task {
background: none;
border: 0;
margin: 0;
min-height: 0;
.taskboard-task-inner {
padding: .2rem;
}
.taskboard-tagline,
.taskboard-text {
display: none;
}
.avatar {
height: 35px;
width: 35px;
}
.icon {
display: none;
}
&.ui-sortable-helper {
box-shadow: none;
}
}
&.task-column,
.task-column {
@include table-flex(flex-start);
@include flex-direction(row);
}
.avatar-task-link {
display: block;
}
.avatar-assigned-to {
display: none;
}
}
.taskboard-table {
overflow: hidden;
@ -28,7 +63,6 @@ $column-margin: 0 10px 0 0;
@extend %large;
background: $whitish;
border-top: 3px solid $gray-light;
cursor: pointer;
margin: $column-margin;
max-width: $column-width;
padding: .5rem 1rem;
@ -38,15 +72,6 @@ $column-margin: 0 10px 0 0;
&:last-child {
margin-right: 0;
}
&:hover {
.icon {
&.hfold,
&.hunfold {
@include transition(opacity .2s linear);
opacity: 1;
}
}
}
.icon {
@extend %medium;
@include transition(color .2s linear);
@ -57,18 +82,14 @@ $column-margin: 0 10px 0 0;
}
&.hfold,
&.hunfold {
@include transition(opacity .2s linear);
@include transform(rotate(90deg));
display: inline-block;
opacity: 0;
}
}
&.column-fold {
@include align-items(center);
@include justify-content(center);
max-width: $column-folded-width;
padding: .3rem 0;
width: $column-folded-width;
span {
display: none;
}
@ -88,39 +109,22 @@ $column-margin: 0 10px 0 0;
overflow-y: scroll;
width: 100%;
.task-column {
@include table-flex-child();
@include table-flex-child($column-flex, $column-width, $column-shrink, $column-width);
margin: $column-margin;
max-width: $column-width;
width: $column-width;
&:last-child {
margin-right: 0;
}
&.colum-fold {
max-width: $column-folded-width;
width: $column-folded-width;
.taskboard-task {
background: none;
border: 0;
margin: 0;
min-height: 0;
.taskboard-task-inner {
padding: .2rem;
}
.taskboard-tagline,
.taskboard-text {
display: none;
}
.avatar {
height: 35px;
width: 35px;
}
.icon {
display: none;
}
&.ui-sortable-helper {
box-shadow: none;
}
}
}
.row-fold {
@extend %fold;
}
.column-fold {
@extend %fold;
.taskboard-task {
max-width: 40px;
width: 40px;
}
}
.task-row {
@ -164,35 +168,6 @@ $column-margin: 0 10px 0 0;
display: none;
}
}
.task-column {
@include table-flex();
@include flex-direction(row);
}
.taskboard-task {
background: none;
border: 0;
margin: 0;
max-width: 40px;
min-height: 0;
width: 40px;
.taskboard-task-inner {
padding: .2rem;
}
.taskboard-tagline,
.taskboard-text {
display: none;
}
.avatar {
height: 35px;
width: 35px;
}
.icon {
display: none;
}
&.ui-sortable-helper {
box-shadow: none;
}
}
}
}
.taskboard-tasks-box {
@ -213,12 +188,19 @@ $column-margin: 0 10px 0 0;
&.icon-plus {
right: 2rem;
}
&.icon-vfold {
&.icon-vfold,
&.icon-vunfold {
left: 0;
right: inherit;
}
}
}
.avatar-task-link {
display: none;
}
.avatar-assigned-to {
display: block;
}
}
.taskboard-userstory-box {