Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added drug and drop for stories #222

Open
wants to merge 1 commit into
base: stable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions app/coffee/modules/taskboard/main.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
project: @projectService.project.get('slug'),
ref: @params.ref
})
@.pendingDrag = []

return if @.applyStoredFilters(@params.pslug, "tasks-filters", @.validQueryParams)

Expand Down Expand Up @@ -358,6 +359,10 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
else
@.loadTasks() if itemsMoved.tasks
@.loadIssues() if itemsMoved.issues

@scope.$on("sprint:us:move", @.moveUs)
@scope.$on "sprint:us:moved", () =>
@.firstLoad()

onAssignedToChanged: (ctx, userid, model) ->
if model.getName() == 'tasks'
Expand Down Expand Up @@ -771,6 +776,61 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
return @location.search().order_by
else
return "created_date"

moveUs: (ctx, usList, newUsIndex, previousUs, nextUs) ->
sprintId = usList[0].milestone
project = usList[0].project

bulkUserstories = _.map(usList, (it) ->
return it.id
)

if ctx
@.pendingDrag.push({
usList: usList,
newUsIndex: newUsIndex,
sprintId: sprintId,
previousUs: previousUs,
nextUs: nextUs
})

@scope.visibleUserStories = _.map @scope.userstories, (it) ->
return it.ref

if ctx && @.pendingDrag.length > 1
return

promise = @rs.userstories.bulkUpdateBacklogOrder(
project,
sprintId,
previousUs,
nextUs,
bulkUserstories
)

promise.then (result) =>
for updatedUs in result.data
usList.forEach (us, index) =>
if us.id == updatedUs.id
us.milestone = updatedUs.milestone
us.backlog_order = updatedUs.backlog_order

@.pendingDrag.shift()

if @.pendingDrag.length
@scope.$applyAsync () =>
@.moveUs(
null
@.pendingDrag[0].usList,
@.pendingDrag[0].newUsIndex,
@.pendingDrag[0].sprintId,
@.pendingDrag[0].previousUs,
@.pendingDrag[0].nextUs,
)
else
@rootscope.$broadcast("sprint:us:moved")

return promise

module.controller("TaskboardController", TaskboardController)

Expand Down
115 changes: 115 additions & 0 deletions app/coffee/modules/taskboard/sortable-us.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
###
# This source code is licensed under the terms of the
# GNU Affero General Public License found in the LICENSE file in
# the root directory of this source tree.
#
# Copyright (c) 2021-present Kaleidos INC
###

taiga = @.taiga
bindOnce = @.taiga.bindOnce

module = angular.module("taigaBacklog")


#############################################################################
## Sortable Directive
#############################################################################

TaskboardUsSortableDirective = () ->
link = ($scope, $el, $attrs) ->
if !$scope.ctrl
console.error('BacklogSortableDirective must have access to to BacklogCtrl')

bindOnce $scope, "project", (project) ->
# If the user has not enough permissions we don't enable the sortable
if not (project.my_permissions.indexOf("modify_us") > -1)
return

initIsBacklog = false
emptyBacklog = $('.js-empty-backlog')
previousUs = null
nextUs = null
oldIndex = null

drake = dragula([$el[0], emptyBacklog[0], emptyBacklog[1]], {
copySortSource: false,
copy: false,
isContainer: (el) ->
return $(el).hasClass('taskboard-draggable-rows')
moves: (item, source, handle, sibling) ->
return $(handle).is('.taskboard-us')
})

drake.on 'drop', (item, target, source, sibling) ->
previousUs = null
nextUs = null

prev = $(item).prevAll('.taskboard-row:not(.gu-transit)')
next = $(item).nextAll('.taskboard-row:not(.gu-transit)')

previousUs = null
if prev.length && prev[0].dataset.id
previousUs = Number(prev[0].dataset.id)

nextUs = null
if !previousUs && next.length && next[0].dataset.id
nextUs = Number(next[0].dataset.id)

drake.on 'drag', (item, container) ->
parent = $(item).parent()
$(document.body).addClass("drag-active")

window.dragMultiple.start(item, container)
dragMultipleItems = window.dragMultiple.getElements()

firstElement = if dragMultipleItems.length then dragMultipleItems[0] else item

parentEl = item.parentNode
oldIndex = $(firstElement).index()

drake.on 'cloned', (item) ->
$(item).addClass('multiple-drag-mirror')

drake.on 'dragend', (item) ->
$('.doom-line').remove()

parent = $(item).parent()

dragMultipleItems = window.dragMultiple.stop()

$(document.body).removeClass("drag-active")

sprint = null

firstElement = if dragMultipleItems.length then dragMultipleItems[0] else item

index = $(firstElement).index()
if index == oldIndex
return

if dragMultipleItems.length
usList = _.map dragMultipleItems, (item) ->
return item = $(item).scope().us
else if $(item).scope()
usList = [$(item).scope().us]

$scope.$applyAsync () =>
$scope.ctrl.moveUs("sprint:us:move", usList, index, previousUs, nextUs)

scroll = autoScroll([window], {
margin: 20,
pixels: 30,
scrollWhenOutside: true,
autoScroll: () ->
return this.down && drake.dragging
})

$scope.$on "$destroy", ->
$el.off()
drake.destroy()

return {link: link}


module.directive("tgTaskboardUsSortable", TaskboardUsSortableDirective)
160 changes: 82 additions & 78 deletions app/partials/includes/modules/taskboard-table.jade
Original file line number Diff line number Diff line change
Expand Up @@ -62,93 +62,97 @@ div.taskboard-table(
ng-class="{'column-fold':statusesFolded[st.id]}",
tg-bind-scope
)

div.taskboard-row(
ng-repeat="us in userstories track by us.id",
ng-class="{blocked: us.is_blocked, 'row-fold':usFolded[us.id], empty: !tasksByUs.get(us.id).size}"
taskboard-draggable-rows(
tg-taskboard-us-sortable
)
div.taskboard-row-title-box.taskboard-column(tg-bo-title="us.blocked_note")
.folding-actions(ng-click='foldUs(us.id)')
tg-svg.vfold.fold-action(
svg-icon="icon-arrow-up",
title="{{'TASKBOARD.TABLE.TITLE_ACTION_FOLD_ROW' | translate}}"
ng-class='{hidden:usFolded[us.id]}'
)

tg-svg.vunfold.fold-action(
svg-icon="icon-arrow-down",
title="{{'TASKBOARD.TABLE.TITLE_ACTION_UNFOLD_ROW' | translate}}"
ng-class='{hidden:!usFolded[us.id]}'
)

.taskboard-us
h3.us-title
a(href="",
tg-nav="project-userstories-detail:project=project.slug,ref=us.ref",
tg-nav-get-params="{\"milestone\": {{us.milestone}}}",
tg-bo-title="'#' + us.ref + ' ' + us.subject")
span.us-ref(tg-bo-ref="us.ref")
span.us-subject(ng-bind-html="us.subject | emojify")
tg-belong-to-epics(
format="pill"
ng-if="us.epics"
epics="us.epics"
)
div.us-data
p.points-value(data-id="{{us.id}}")
span.card-estimation(
ng-if="us.total_points"
title="{{ 'COMMON.CARD.ESTIMATION' | translate }}"
translate="COMMON.CARD.PTS"
translate-values="{ 'pts': us.total_points}"
)
div.taskboard-row(
ng-repeat="us in userstories track by us.id",
ng-class="{blocked: us.is_blocked, 'row-fold':usFolded[us.id], empty: !tasksByUs.get(us.id).size}"
tg-bind-scope
data-id="{{ us.id }}"
)
div.taskboard-row-title-box.taskboard-column(tg-bo-title="us.blocked_note")
.folding-actions(ng-click='foldUs(us.id)')
tg-svg.vfold.fold-action(
svg-icon="icon-arrow-up",
title="{{'TASKBOARD.TABLE.TITLE_ACTION_FOLD_ROW' | translate}}"
ng-class='{hidden:usFolded[us.id]}'
)

span.card-estimation.not-estimated(
ng-if="!us.total_points",
translate="US.NOT_ESTIMATED"
)
tg-svg.vunfold.fold-action(
svg-icon="icon-arrow-down",
title="{{'TASKBOARD.TABLE.TITLE_ACTION_UNFOLD_ROW' | translate}}"
ng-class='{hidden:!usFolded[us.id]}'
)

tg-due-date.due-date(
due-date="us.due_date"
is-closed="us.is_closed"
ng-if="us.due_date"
obj-type="us"
.taskboard-us
h3.us-title
a(href="",
tg-nav="project-userstories-detail:project=project.slug,ref=us.ref",
tg-nav-get-params="{\"milestone\": {{us.milestone}}}",
tg-bo-title="'#' + us.ref + ' ' + us.subject")
span.us-ref(tg-bo-ref="us.ref")
span.us-subject(ng-bind-html="us.subject | emojify")
tg-belong-to-epics(
format="pill"
ng-if="us.epics"
epics="us.epics"
)
div.us-data
p.points-value(data-id="{{us.id}}")
span.card-estimation(
ng-if="us.total_points"
title="{{ 'COMMON.CARD.ESTIMATION' | translate }}"
translate="COMMON.CARD.PTS"
translate-values="{ 'pts': us.total_points}"
)

span.card-estimation.not-estimated(
ng-if="!us.total_points",
translate="US.NOT_ESTIMATED"
)

tg-due-date.due-date(
due-date="us.due_date"
is-closed="us.is_closed"
ng-if="us.due_date"
obj-type="us"
)

span.card-lock(ng-if="us.is_blocked")
tg-svg(svg-icon="icon-lock")
span.card-lock(ng-if="us.is_blocked")
tg-svg(svg-icon="icon-lock")

p.status-value(ng-if="usStatusById[us.status]")
span(
ng-style="{'color': usStatusById[us.status]['color']}"
ng-bind="usStatusById[us.status]['name']")
p.status-value(ng-if="usStatusById[us.status]")
span(
ng-style="{'color': usStatusById[us.status]['color']}"
ng-bind="usStatusById[us.status]['name']")

.add-new-task
include ../components/addnewtask
.add-new-task
include ../components/addnewtask

div.taskboard-cards-box.taskboard-column(
ng-repeat="st in ::taskStatusList track by st.id",
class="squish-status-{{st.id}}",
ng-class="{'column-fold':statusesFolded[st.id], 'row-max-two': usTasks.getIn([us.id.toString(), st.id.toString()]).size === 2, 'row-max-three': usTasks.getIn([us.id.toString(), st.id.toString()]).size >= 3}",
tg-bind-scope
)
tg-card.card.ng-animate-disabled(
data-id="{{ taskId }}"
tg-repeat="taskId in usTasks.getIn([us.id.toString(), st.id.toString()])"
ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id)}"
tg-class-permission="{'readonly': '!modify_task'}"
on-toggle-fold="ctrl.toggleFold(id, 'tasks')"
on-click-edit="ctrl.editTask(id)"
on-click-delete="ctrl.deleteTask(id)"
on-click-assigned-to="ctrl.changeTaskAssignedTo(id)"
project="project"
item="taskMap.get(taskId)"
zoom="ctrl.zoom"
zoom-level="ctrl.zoomLevel"
in-view-port="true"
type="task"
folded="statusesFolded[st.id] || usFolded[us.id]"
div.taskboard-cards-box.taskboard-column(
ng-repeat="st in ::taskStatusList track by st.id",
class="squish-status-{{st.id}}",
ng-class="{'column-fold':statusesFolded[st.id], 'row-max-two': usTasks.getIn([us.id.toString(), st.id.toString()]).size === 2, 'row-max-three': usTasks.getIn([us.id.toString(), st.id.toString()]).size >= 3}",
tg-bind-scope
)
tg-card.card.ng-animate-disabled(
data-id="{{ taskId }}"
tg-repeat="taskId in usTasks.getIn([us.id.toString(), st.id.toString()])"
ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id)}"
tg-class-permission="{'readonly': '!modify_task'}"
on-toggle-fold="ctrl.toggleFold(id, 'tasks')"
on-click-edit="ctrl.editTask(id)"
on-click-delete="ctrl.deleteTask(id)"
on-click-assigned-to="ctrl.changeTaskAssignedTo(id)"
project="project"
item="taskMap.get(taskId)"
zoom="ctrl.zoom"
zoom-level="ctrl.zoomLevel"
in-view-port="true"
type="task"
folded="statusesFolded[st.id] || usFolded[us.id]"
)
div.taskboard-row(
ng-init="us = null",
ng-class="{'row-fold':usFolded[null], empty: !tasksByUs.get(null).size}"
Expand Down
4 changes: 4 additions & 0 deletions app/styles/modules/backlog/taskboard-table.scss
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ $fold-animation-duration: .1s;
}
}

.taskboard-row {
cursor: move;
}

@mixin fold {
&.taskboard-row {
min-height: 50px;
Expand Down