kanban drag multiple

stable
Jesús Espino 2017-11-08 09:38:01 +01:00 committed by Alex Hermida
parent 6fe84f9acd
commit 2cd47a578c
13 changed files with 198 additions and 68 deletions

View File

@ -68,7 +68,7 @@ BacklogSortableDirective = () ->
window.dragMultiple.start(item, container)
drake.on 'cloned', (item) ->
$(item).addClass('backlog-us-mirror')
$(item).addClass('multiple-drag-mirror')
drake.on 'dragend', (item) ->
parent = $(item).parent()

View File

@ -251,8 +251,6 @@ Qqueue = ($q) ->
bindAdd: (fn) =>
return (args...) =>
lastPromise = lastPromise.then () => fn.apply(@, args)
return qqueue
add: (fn) =>
if !lastPromise
lastPromise = fn()

View File

@ -93,15 +93,17 @@ class KanbanUserstoriesService extends taiga.Service
@.refresh()
move: (id, statusId, index) ->
us = @.getUsModel(id)
move: (usList, statusId, index) ->
initialLength = usList.length
usByStatus = _.filter @.userstoriesRaw, (it) =>
return it.status == statusId
usByStatus = _.sortBy usByStatus, (it) => @.order[it.id]
usByStatusWithoutMoved = _.filter usByStatus, (it) => it.id != id
usByStatusWithoutMoved = _.filter usByStatus, (listIt) ->
return !_.find usList, (moveIt) -> return listIt.id == moveIt.id
beforeDestination = _.slice(usByStatusWithoutMoved, 0, index)
afterDestination = _.slice(usByStatusWithoutMoved, index)
@ -112,26 +114,54 @@ class KanbanUserstoriesService extends taiga.Service
previousWithTheSameOrder = _.filter beforeDestination, (it) =>
@.order[it.id] == @.order[previous.id]
if previousWithTheSameOrder.length > 1
for it in previousWithTheSameOrder
setOrders[it.id] = @.order[it.id]
if !previous and (!afterDestination or afterDestination.length == 0)
@.order[us.id] = 0
else if !previous and afterDestination and afterDestination.length > 0
@.order[us.id] = @.order[afterDestination[0].id] - 1
modifiedUs = []
setPreviousOrders = []
setNextOrders = []
if !previous
startIndex = 0
else if previous
@.order[us.id] = @.order[previous.id] + 1
startIndex = @.order[previous.id] + 1
for it, key in afterDestination
@.order[it.id] = @.order[us.id] + key + 1
previousWithTheSameOrder = _.filter(beforeDestination, (it) =>
it.kanban_order == @.order[previous.id]
)
for it, key in afterDestination # increase position of the us after the dragged us's
@.order[it.id] = @.order[previous.id] + key + initialLength + 1
it.kanban_order = @.order[it.id]
us.status = statusId
us.kanban_order = @.order[us.id]
setNextOrders = _.map(afterDestination, (it) =>
{us_id: it.id, order: @.order[it.id]}
)
# we must send the USs previous to the dropped USs to tell the backend
# which USs are before the dropped USs, if they have the same value to
# order, the backend doens't know after which one do you want to drop
# the USs
if previousWithTheSameOrder.length > 1
setPreviousOrders = _.map(previousWithTheSameOrder, (it) =>
{us_id: it.id, order: @.order[it.id]}
)
for us, key in usList
us.status = statusId
us.kanban_order = startIndex + key
@.order[us.id] = us.kanban_order
modifiedUs.push({us_id: us.id, order: us.kanban_order})
@.refresh()
return {"us_id": us.id, "order": @.order[us.id], "set_orders": setOrders}
return {
bulkOrders: modifiedUs.concat(setPreviousOrders, setNextOrders),
usList: modifiedUs,
set_orders: setOrders
}
moveToEnd: (id, statusId) ->
us = @.getUsModel(id)

View File

@ -71,6 +71,7 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
bindMethods(@)
@kanbanUserstoriesService.reset()
@.openFilter = false
@.selectedUss = {}
return if @.applyStoredFilters(@params.pslug, "kanban-filters")
@ -80,6 +81,13 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
taiga.defineImmutableProperty @.scope, "usByStatus", () =>
return @kanbanUserstoriesService.usByStatus
cleanSelectedUss: () ->
for key of @.selectedUss
@.selectedUss[key] = false
toggleSelectedUs: (usId) ->
@.selectedUss[usId] = !@.selectedUss[usId]
firstLoad: () ->
promise = @.loadInitialData()
@ -291,36 +299,42 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
prepareBulkUpdateData: (uses, field="kanban_order") ->
return _.map(uses, (x) -> {"us_id": x.id, "order": x[field]})
moveUs: (ctx, us, oldStatusId, newStatusId, index) ->
us = @kanbanUserstoriesService.getUsModel(us.get('id'))
newStatus = @scope.usStatusById[newStatusId]
if newStatus.is_archived and !@scope.usByStatus.get(newStatusId.toString())
moveUpdateData = @kanbanUserstoriesService.moveToEnd(us.id, newStatusId)
else
moveUpdateData = @kanbanUserstoriesService.move(us.id, newStatusId, index)
moveUs: (ctx, usList, newStatusId, index) ->
@.cleanSelectedUss()
usList = _.map usList, (us) =>
return @kanbanUserstoriesService.getUsModel(us.id)
params = {
include_attachments: true,
include_tasks: true
}
data = @kanbanUserstoriesService.move(usList, newStatusId, index)
options = {
headers: {
"set-orders": JSON.stringify(moveUpdateData.set_orders)
promise = @rs.userstories.bulkUpdateKanbanOrder(@scope.projectId, data.bulkOrders)
promise.then () =>
# saving
# drag single or different status
options = {
headers: {
"set-orders": JSON.stringify(data.setOrders)
}
}
}
promise = @repo.save(us, true, params, options, true)
params = {
include_attachments: true,
include_tasks: true
}
promise = promise.then (result) =>
headers = result[1]
promises = _.map usList, (us) =>
@repo.save(us, true, params, options, true)
if headers && headers['taiga-info-order-updated']
order = JSON.parse(headers['taiga-info-order-updated'])
@kanbanUserstoriesService.assignOrders(order)
@scope.$broadcast("redraw:wip")
promise = @q.all(promises)
return promise
promise.then (result) =>
headers = result[1]
if headers && headers['taiga-info-order-updated']
order = JSON.parse(headers['taiga-info-order-updated'])
@kanbanUserstoriesService.assignOrders(order)
@scope.$broadcast("redraw:wip")
module.controller("KanbanController", KanbanController)

View File

@ -48,7 +48,6 @@ KanbanSortableDirective = ($repo, $rs, $rootscope) ->
if not ($scope.project.my_permissions.indexOf("modify_us") > -1)
return
oldParentScope = null
newParentScope = null
itemEl = null
tdom = $el
@ -70,23 +69,44 @@ KanbanSortableDirective = ($repo, $rs, $rootscope) ->
})
drake.on 'drag', (item) ->
oldParentScope = $(item).parent().scope()
window.dragMultiple.start(item, containers)
drake.on 'cloned', (item, dropTarget) ->
$(item).addClass('multiple-drag-mirror')
drake.on 'dragend', (item) ->
parentEl = $(item).parent()
itemEl = $(item)
itemUs = itemEl.scope().us
itemIndex = itemEl.index()
newParentScope = parentEl.scope()
newStatusId = newParentScope.s.id
oldStatusId = oldParentScope.s.id
dragMultipleItems = window.dragMultiple.stop()
if newStatusId != oldStatusId
deleteElement(itemEl)
# if it is not drag multiple
if !dragMultipleItems.length
dragMultipleItems = [item]
$scope.$apply ->
$rootscope.$broadcast("kanban:us:move", itemUs, itemUs.getIn(['model', 'status']), newStatusId, itemIndex)
firstElement = dragMultipleItems[0]
index = $(firstElement).index()
newStatus = newParentScope.s.id
usList = _.map dragMultipleItems, (item) -> $(item).scope().us
finalUsList = _.map usList, (item) ->
return {
id: item.get('id'),
oldStatusId: item.getIn(['model', 'status'])
}
$scope.$apply ->
_.each usList, (item, key) =>
oldStatus = item.getIn(['model', 'status'])
sameContainer = newStatus == oldStatus
if !sameContainer
itemEl = $(dragMultipleItems[key])
deleteElement(itemEl)
$rootscope.$broadcast("kanban:us:move", finalUsList, newStatus, index)
scroll = autoScroll(containers, {
margin: 100,

View File

@ -7,8 +7,8 @@
var reset = function(elm) {
$(elm)
.removeAttr('style')
.removeClass('tg-backlog-us-mirror')
.removeClass('backlog-us-mirror')
.removeClass('tg-multiple-drag-mirror')
.removeClass('multiple-drag-mirror')
.data('dragMultipleIndex', null)
.data('dragMultipleActive', false);
};
@ -40,6 +40,8 @@
var currentTop = shadow.position().top;
var height = shadow.outerHeight();
$('.gu-transit').addClass('gu-transit-multi');
_.forEach(dragMultiple.items.draggingItems, function(elm, index) {
var elmIndex = parseInt(elm.data('dragMultipleIndex'), 10);
var top = currentTop + (elmIndex * height);
@ -57,22 +59,21 @@
refreshOriginal();
var current = dragMultiple.items.elm;
var container = dragMultiple.items.container;
document.documentElement.removeEventListener('mousemove', removeEventFn);
// reset
dragMultiple.items = {};
$('.' + mainClass).removeClass(mainClass);
$('.tg-backlog-us-mirror').remove();
$('.backlog-us-mirror').removeClass('backlog-us-mirror');
$('.tg-multiple-drag-mirror').remove();
$('.multiple-drag-mirror').removeClass('multiple-drag-mirror');
$('.tg-backlog-us-dragging')
.removeClass('tg-backlog-us-dragging')
$('.tg-multiple-drag-dragging')
.removeClass('tg-multiple-drag-dragging')
.show();
$('.gu-transit-multi').removeClass('gu-transit-multi');
return $('.' + multipleSortableClass);
};
@ -180,8 +181,8 @@
clone = $(item).clone(true);
clone
.addClass('backlog-us-mirror')
.addClass('tg-backlog-us-mirror')
.addClass('multiple-drag-mirror')
.addClass('tg-multiple-drag-mirror')
.data('dragmultiple:originalPosition', $(item).position())
.data('dragMultipleActive', true)
.css({
@ -194,7 +195,7 @@
$(item)
.hide()
.addClass('tg-backlog-us-dragging');
.addClass('tg-multiple-drag-dragging');
return clone;
});

View File

@ -23,7 +23,7 @@
tg-check-permission="{{vm.getPermissionsKey()}}"
)
a.e2e-assign.card-owner-assign(
ng-click="vm.onClickAssignedTo({id: vm.item.get('id')})"
ng-click="!$event.ctrlKey && vm.onClickAssignedTo({id: vm.item.get('id')})"
href=""
)
tg-svg(svg-icon="icon-add-user")
@ -31,7 +31,7 @@
a.e2e-edit.card-edit(
href=""
ng-click="vm.onClickEdit({id: vm.item.get('id')})"
ng-click="!$event.ctrlKey && vm.onClickEdit({id: vm.item.get('id')})"
tg-loading="vm.item.get('loading')"
)
tg-svg(svg-icon="icon-edit")

View File

@ -1,5 +1,5 @@
.card-unfold.ng-animate-disabled(
ng-click="vm.toggleFold()"
ng-click="!$event.ctrlKey && vm.toggleFold()"
ng-if="vm.visible('unfold') && (vm.hasTasks() || vm.hasVisibleAttachments())"
role="button"
)

View File

@ -14,3 +14,15 @@
images="vm.item.get('images')"
)
include card-templates/card-unfold
.card-transit-multi
div.fake-us
div.fake-img
div.column
div.fake-text
div.fake-text
div.fake-us
div.fake-img
div.column
div.fake-text
div.fake-text

View File

@ -67,7 +67,7 @@ div.kanban-table(
tg-card.card.ng-animate-disabled(
tg-repeat="us in usByStatus.get(s.id.toString()) track by us.getIn(['model', 'id'])",
ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id)}"
ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id), 'kanban-task-selected': ctrl.selectedUss[us.get('id')], 'ui-multisortable-multiple': ctrl.selectedUss[us.get('id')]}"
tg-class-permission="{'readonly': '!modify_task'}"
tg-bind-scope,
on-toggle-fold="ctrl.toggleFold(id)"
@ -78,6 +78,7 @@ div.kanban-table(
zoom="ctrl.zoom"
zoom-level="ctrl.zoomLevel"
archived="ctrl.isUsInArchivedHiddenStatus(us.get('id'))"
ng-click="$event.ctrlKey && ctrl.toggleSelectedUs(us.get('id'))"
)
div.kanban-column-intro(ng-if="s.is_archived", tg-kanban-archived-status-intro="s")

View File

@ -41,7 +41,7 @@
}
}
.backlog-us-mirror {
.multiple-drag-mirror.us-item-row {
background: $white;
border-radius: 4px;
box-shadow: 2px 2px 5px $gray;

View File

@ -148,9 +148,63 @@ $column-padding: .5rem 1rem;
.kanban-uses-box {
background: $mass-white;
}
.kanban-task-selected {
&.card:not(.gu-transit-multi) {
// border: 1px solid $primary-light;
box-shadow: 0 0 0 1px $primary-light, 2px 2px 4px darken($whitish, 10%);
}
}
}
.kanban-table-inner {
display: flex;
flex-wrap: nowrap;
}
.card-transit-multi {
background: darken($whitish, 2%);
border: 1px dashed darken($whitish, 8%);
display: none;
opacity: 1;
padding: 1rem;
.fake-img,
.fake-text {
background: darken($whitish, 8%);
}
.fake-us {
display: flex;
margin-bottom: 1rem;
&:last-child {
margin-bottom: 0;
}
}
.column {
padding-left: 0.5rem;
width: 100%;
}
.fake-img {
flex-basis: 48px;
flex-shrink: 0;
height: 48px;
width: 48px;
}
.fake-text {
height: 1rem;
margin-bottom: 1rem;
width: 80%;
&:last-child {
margin-bottom: 0;
width: 40%;
}
}
}
.card.gu-transit-multi {
.card-transit-multi {
display: block;
}
.card-inner {
display: none;
}
}

File diff suppressed because one or more lines are too long