Skip to content

Commit

Permalink
Implement manual sorting
Browse files Browse the repository at this point in the history
Signed-off-by: Raimund Schlüßler <raimund.schluessler@mailbox.org>
  • Loading branch information
raimund-schluessler committed May 9, 2020
1 parent 0b75a29 commit 64fc9b2
Show file tree
Hide file tree
Showing 18 changed files with 491 additions and 75 deletions.
23 changes: 14 additions & 9 deletions src/components/SortorderDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
<script>
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import { mapGetters } from 'vuex'
export default {
name: 'SortorderDropdown',
Expand Down Expand Up @@ -96,34 +97,38 @@ export default {
text: this.$t('tasks', 'Priority'),
hint: this.$t('tasks', 'Sort by priority and summary.'),
},
// Manual sorting is not yet implemented
// {
// id: 'manual',
// icon: 'icon-manual',
// text: this.$t('tasks', 'Manually'),
// hint: this.$t('tasks', 'Sort by manual order.')
// },
{
id: 'alphabetically',
icon: 'icon-alphabetically',
text: this.$t('tasks', 'Alphabetically'),
hint: this.$t('tasks', 'Sort by summary and priority.'),
},
{
id: 'manual',
icon: 'icon-manual',
text: this.$t('tasks', 'Manually'),
hint: this.$t('tasks', 'Sort by manual order.'),
},
],
}
},
computed: {
...mapGetters({
sortOrderGetter: 'sortOrder',
sortDirectionGetter: 'sortDirection',
}),
sortOrder: {
get() {
return this.$store.state.settings.settings.sortOrder
return this.sortOrderGetter
},
set(order) {
this.$store.dispatch('setSetting', { type: 'sortOrder', value: order })
},
},
sortDirection: {
get() {
return this.$store.state.settings.settings.sortDirection
return this.sortDirectionGetter
},
set(direction) {
this.$store.dispatch('setSetting', { type: 'sortDirection', value: +direction })
Expand Down
17 changes: 5 additions & 12 deletions src/components/TaskBody.vue
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,11 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
</form>
</div>
<TaskDragContainer v-if="showSubtasks"
:tasks="filteredSubtasks"
:disabled="task.calendar.readOnly"
:collection-string="collectionString"
:task-id="task.uri"
:calendar-id="task.calendar.uri"
:disabled="task.calendar.readOnly">
<TaskBody v-for="subtask in filteredSubtasks"
:key="subtask.uid"
:task="subtask"
:collection-string="collectionString"
class="subtask" />
</TaskDragContainer>
:calendar-id="task.calendar.uri" />
</div>
</li>
</template>
Expand Down Expand Up @@ -186,7 +182,6 @@ export default {
collectionString: {
type: String,
default: null,
required: false,
},
},
data() {
Expand All @@ -203,8 +198,6 @@ export default {
},
computed: {
...mapGetters({
sortOrder: 'sortOrder',
sortDirection: 'sortDirection',
searchQuery: 'searchQuery',
}),
Expand Down Expand Up @@ -305,7 +298,7 @@ export default {
return isTaskInList(task, this.collectionString) || this.isTaskOpen(task) || this.isDescendantOpen(task)
})
}
return sort([...subTasks], this.sortOrder, this.sortDirection)
return subTasks
},
/**
Expand Down
130 changes: 127 additions & 3 deletions src/components/TaskDragContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,47 +25,166 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
:set-data="setDragData"
v-bind="{group: 'tasks', swapThreshold: 0.30, delay: 500, delayOnTouchOnly: true, touchStartThreshold: 3, disabled: disabled, filter: '.readOnly'}"
:move="onMove"
@add="onAdd">
<slot />
@add="onAdd"
@end="onEnd">
<TaskBody v-for="task in sortedTasks"
:key="task.key"
:task="task"
:collection-string="collectionString" />
</draggable>
</template>

<script>
import { sort } from '../store/storeHelper'
import draggable from 'vuedraggable'
import { mapGetters, mapActions } from 'vuex'
import { mapGetters, mapActions, mapMutations } from 'vuex'
export default {
name: 'TaskDragContainer',
components: {
/**
* We asynchronously import here, because we have a circular dependency
* between TaskDragContainer and TaskBody which otherwise cannot be resolved.
* See https://vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components
*
* We load it "eager", because the TaskBody will always be required.
*
* @returns {Object} The TaskBody component
*/
TaskBody: () => import(/* webpackMode: "eager" */ './TaskBody'),
draggable,
},
props: {
tasks: {
type: Array,
default: () => [],
},
disabled: {
type: Boolean,
default: false,
},
collectionString: {
type: String,
default: null,
},
},
computed: {
...mapGetters({
getCalendar: 'getCalendarById',
getTask: 'getTaskByUri',
sortOrder: 'sortOrder',
sortDirection: 'sortDirection',
}),
sortedTasks() {
return sort([...this.tasks], this.sortOrder, this.sortDirection)
},
},
methods: {
...mapActions([
'moveTask',
'setPriority',
'setPercentComplete',
'setDate',
'setSortOrder',
]),
...mapMutations({
commitSortOrder: 'setSortOrder',
}),
setDragData: (dataTransfer) => {
// We do nothing here, this just prevents
// vue.draggable from setting data on the
// dataTransfer object.
},
adjustSortOrder(task, newIndex, oldIndex = -1) {
// Only change the sort order if we sort manually
if (this.sortOrder !== 'manual') {
return
}
// If the tasks array has no entry, we don't need to sort.
if (this.sortedTasks.length === 0) {
return
}
// If the task is inserted at its current position, don't sort.
if (newIndex === oldIndex) {
return
}
// Get a copy of the sorted tasks array
const sortedTasks = [...this.sortedTasks]
// In case the task to move is already in the array, move it to the new position
if (oldIndex > -1) {
sortedTasks.splice(newIndex, 0, sortedTasks.splice(oldIndex, 1)[0])
// Otherwise insert it
} else {
sortedTasks.splice(newIndex, 0, task)
}
// Get the new sort order for the moved task and apply it.
// We just do that to minimize the number of other tasks to be changed.
let newSortOrder
if (newIndex + 1 < sortedTasks.length) {
newSortOrder = sortedTasks[newIndex + 1].sortOrder - Math.pow(-1, +this.sortDirection)
} else {
newSortOrder = sortedTasks[newIndex - 1].sortOrder + Math.pow(-1, +this.sortDirection)
}
if (newSortOrder < 0) {
newSortOrder = 0
}
// If we moved the task from a different list, don't schedule a request to the server,
// this will be done afterwards.
const newOrder = { task: sortedTasks[newIndex], order: newSortOrder }
if (oldIndex > -1) {
this.setSortOrder(newOrder)
} else {
this.commitSortOrder(newOrder)
}
// Check the sort orders to be strictly monotonous
if (this.sortDirection) {
sortedTasks.reverse()
}
let currentIndex = 1
while (currentIndex < sortedTasks.length) {
if (sortedTasks[currentIndex].sortOrder <= sortedTasks[currentIndex - 1].sortOrder) {
const order = { task: sortedTasks[currentIndex], order: sortedTasks[currentIndex - 1].sortOrder + 1 }
if (sortedTasks[currentIndex] === task) {
this.commitSortOrder(order)
} else {
this.setSortOrder(order)
}
}
currentIndex++
}
},
/**
* Called when a task is dropped.
* We only handle sorting tasks here.
*
* @param {Object} $event The event which caused the drop
*/
onEnd($event) {
// Don't do anything if the tasks are not sorted but moved.
if ($event.to !== $event.from) {
return
}
/**
* We have to adjust the sortOrder property of the tasks
* to achieve the desired sort order.
*/
this.adjustSortOrder(null, $event.newIndex, $event.oldIndex)
},
/**
* Called when a task is dropped.
* We handle changing the parent task, calendar or collection here
* and also have to sort a task to the correct position
* in case of manual sort order.
*
* @param {Object} $event The event which caused the drop
*/
Expand All @@ -76,6 +195,11 @@ export default {
if (taskAttribute) {
task = this.getTask(taskAttribute.value)
}
/**
* We have to adjust the sortOrder property of the tasks
* to achieve the desired sort order.
*/
this.adjustSortOrder(task, $event.newIndex, -1)
// Move the task to a new calendar or parent.
this.prepareMoving(task, $event)
this.prepareCollecting(task, $event)
Expand Down
24 changes: 4 additions & 20 deletions src/components/TheCollections/Calendar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,18 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
<div class="task-list">
<div class="grouped-tasks">
<TaskDragContainer
:tasks="uncompletedRootTasks(calendar.tasks)"
:calendar-id="calendarId"
:disabled="calendar.readOnly"
class="tasks"
collection-id="uncompleted"
type="list">
<TaskBody v-for="task in sort(uncompletedRootTasks(calendar.tasks), sortOrder, sortDirection)"
:key="task.key"
:task="task" />
</TaskDragContainer>
collection-id="uncompleted" />
<h2 v-show="completedCount(calendarId)" class="heading heading--hiddentasks reactive" @click="toggleHidden">
<span class="heading__title icon-triangle-s">{{ completedCountString }}</span>
</h2>
<TaskDragContainer v-if="showHidden"
:tasks="completedRootTasks(calendar.tasks)"
:calendar-id="calendarId"
:disabled="calendar.readOnly"
class="completed-tasks"
collection-id="completed"
type="list">
<TaskBody v-for="task in sort(completedRootTasks(calendar.tasks), sortOrder, sortDirection)"
:key="task.key"
:task="task" />
</TaskDragContainer>
collection-id="completed" />
<LoadCompletedButton :calendar="calendar" />
<DeleteCompletedModal v-if="calendar.loadedCompleted && !calendar.readOnly" :calendar="calendar" />
</div>
Expand All @@ -71,16 +61,13 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.

<script>
import { mapGetters, mapActions } from 'vuex'
import { sort } from '../../store/storeHelper'
import SortorderDropdown from '../SortorderDropdown'
import LoadCompletedButton from '../LoadCompletedButton'
import DeleteCompletedModal from '../DeleteCompletedModal'
import TaskBody from '../TaskBody'
import TaskDragContainer from '../TaskDragContainer'
export default {
components: {
TaskBody,
SortorderDropdown,
LoadCompletedButton,
TaskDragContainer,
Expand Down Expand Up @@ -129,15 +116,12 @@ export default {
calendar: 'getCalendarByRoute',
uncompletedRootTasks: 'findUncompletedRootTasks',
completedRootTasks: 'findCompletedRootTasks',
sortOrder: 'sortOrder',
sortDirection: 'sortDirection',
}),
},
methods: {
...mapActions([
'createTask',
]),
sort,
toggleHidden() {
this.showHidden = +!this.showHidden
},
Expand Down
Loading

0 comments on commit 64fc9b2

Please sign in to comment.