Board: Checklist item dnd support (#1873)

Signed-off-by: Anna No <anna.no@xored.com>
This commit is contained in:
Anna No 2022-05-26 16:36:44 +07:00 committed by GitHub
parent c25c5cfdad
commit bbcbb7722a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 100 additions and 23 deletions

View File

@ -125,7 +125,7 @@ export class TTask extends TAttachedDoc implements Task {
todoItems!: number
}
@Model(task.class.TodoItem, core.class.AttachedDoc, DOMAIN_TASK)
@Model(task.class.TodoItem, core.class.AttachedDoc, DOMAIN_TASK, [task.interface.DocWithRank])
@UX(task.string.Todo)
export class TTodoItem extends TAttachedDoc implements TodoItem {
@Prop(TypeMarkup(), task.string.TodoName, task.icon.Task)
@ -143,6 +143,8 @@ export class TTodoItem extends TAttachedDoc implements TodoItem {
@Prop(Collection(task.class.TodoItem), task.string.Todos)
items!: number
declare rank: string
}
@Model(task.class.SpaceWithStates, core.class.Space)

View File

@ -16,7 +16,7 @@
import { Ref } from '@anticrm/core'
import { createQuery, getClient, UserBox, MessageBox } from '@anticrm/presentation'
import type { TodoItem } from '@anticrm/task'
import task from '@anticrm/task'
import task, { calcRank } from '@anticrm/task'
import {
Button,
CheckBox,
@ -45,7 +45,9 @@
let hideDoneItems: boolean = false
let newItemName = ''
let editingItemId: Ref<TodoItem> | undefined = undefined
let hovered: Ref<TodoItem> | undefined
let hovered: Ref<TodoItem> | undefined = undefined
let dragItem: TodoItem | undefined = undefined
let dragOverItem: TodoItem | undefined = undefined
function deleteChecklist () {
if (!value) {
@ -79,11 +81,13 @@
async function addItem (event: CustomEvent<string>) {
newItemName = ''
const prev = checklistItems && checklistItems.length > 0 ? checklistItems[checklistItems.length - 1] : undefined
const item = {
name: event.detail ?? '',
assignee: null,
dueTo: null,
done: false
done: false,
rank: calcRank(prev, undefined)
}
if (item.name.length <= 0) {
return
@ -121,16 +125,49 @@
showPopup(ContextMenu, { object: item }, getEventPopupPositionElement(e))
}
$: checklistItemsQuery.query(task.class.TodoItem, { space: value.space, attachedTo: value._id }, (result) => {
checklistItems = result
done = checklistItems.reduce((result: number, current: TodoItem) => {
return current.done ? result + 1 : result
}, 0)
})
function itemDrop (): void {
if (dragItem === undefined || dragOverItem === undefined || dragItem._id === dragOverItem._id) {
return
}
const index = checklistItems.findIndex((item) => item._id === dragOverItem?._id)
const dragIndex = checklistItems.findIndex((item) => item._id === dragItem?._id)
if (dragIndex > index) {
const prev = index - 1 >= 0 ? checklistItems[index - 1] : undefined
dragItem.rank = calcRank(prev, dragOverItem)
client.update(dragItem, { rank: dragItem.rank })
} else {
const next = index + 1 < checklistItems.length ? checklistItems[index + 1] : undefined
dragItem.rank = calcRank(dragOverItem, next)
client.update(dragItem, { rank: dragItem.rank })
}
}
$: checklistItemsQuery.query(
task.class.TodoItem,
{ space: value.space, attachedTo: value._id },
(result) => {
checklistItems = result
done = checklistItems.reduce((result: number, current: TodoItem) => {
return current.done ? result + 1 : result
}, 0)
},
{
sort: {
rank: 1
}
}
)
</script>
{#if value !== undefined}
<div class="flex-col w-full">
<div
class="flex-col w-full"
on:drop|preventDefault={(ev) => {
itemDrop()
}}
on:dragover|preventDefault
on:dragleave
>
<div class="flex-row-stretch mt-4 mb-2">
{#if isEditingName}
<div class="flex-grow">
@ -178,6 +215,17 @@
<div
class="flex-row-stretch mb-1 mt-1 pl-1 min-h-7 border-radius-1"
class:background-button-noborder-bg-hover={hovered === item._id && editingItemId !== item._id}
draggable={true}
on:dragstart={() => {
dragItem = item
}}
on:dragend={() => {
dragItem = undefined
dragOverItem = undefined
}}
on:dragover={() => {
dragOverItem = item
}}
on:mouseover={() => {
hovered = item._id
}}

View File

@ -3,7 +3,7 @@
import { Card } from '@anticrm/board'
import { WithLookup } from '@anticrm/core'
import task, { TodoItem } from '@anticrm/task'
import task, { calcRank, TodoItem } from '@anticrm/task'
import { translate } from '@anticrm/platform'
import presentation, { createQuery, getClient } from '@anticrm/presentation'
import { Label, Button, Dropdown, EditBox, IconClose } from '@anticrm/ui'
@ -18,6 +18,7 @@
label: ''
}
let lastCardList: TodoItem | undefined = undefined
let name: string | undefined
let selectedTemplate: ListItem | undefined = undefined
let templateListItems: ListItem[] = [noneListItem]
@ -51,7 +52,8 @@
name,
done: false,
dueTo: null,
assignee: null
assignee: null,
rank: calcRank(lastCardList)
}
)
if (items.length > 0) {
@ -61,7 +63,8 @@
name: item.name,
dueTo: item.dueTo,
done: item.done,
assignee: item.assignee
assignee: item.assignee,
rank: item.rank
})
)
)
@ -75,13 +78,17 @@
(result: WithLookup<Card>[]) => {
templateListItems = [noneListItem]
templatesMap = new Map()
lastCardList = undefined
for (const card of result) {
const todoItems = card.$lookup?.todoItems as TodoItem[]
if (!todoItems) {
continue
}
if (card._id === value?._id && todoItems.length > 0) {
todoItems.sort((a, b) => a.rank?.localeCompare(b.rank))
lastCardList = todoItems[todoItems.length - 1]
}
templateListItems.push({
_id: card._id,
label: card.title,

View File

@ -14,9 +14,9 @@
-->
<script lang="ts">
import type { Class, Ref, Space } from '@anticrm/core'
import { Card, getClient } from '@anticrm/presentation'
import type { Task } from '@anticrm/task'
import task from '@anticrm/task'
import { Card, createQuery, getClient } from '@anticrm/presentation'
import type { Task, TodoItem } from '@anticrm/task'
import task, { calcRank } from '@anticrm/task'
import { DatePicker, EditBox, Grid } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import plugin from '../../plugin'
@ -28,24 +28,43 @@
let name: string
const done = false
let dueTo: number | null = null
let latestItem: TodoItem | undefined = undefined
$: _space = space
const dispatch = createEventDispatcher()
const client = getClient()
const todoItemsQuery = createQuery()
export function canClose (): boolean {
return objectId === undefined
}
async function createTodo () {
await client.addCollection(task.class.TodoItem, space, objectId, _class, 'todos', {
await client.addCollection(task.class.TodoItem, space, objectId, _class, 'todoItems', {
name,
assignee: null,
done,
dueTo: dueTo ?? null
dueTo: dueTo ?? null,
rank: calcRank(latestItem)
})
}
$: todoItemsQuery.query(
task.class.TodoItem,
{ attachedTo: objectId },
(result) => {
latestItem = undefined
if (result && result.length > 0) {
latestItem = result[result.length - 1]
}
},
{
sort: {
rank: 1
}
}
)
</script>
<Card

View File

@ -53,8 +53,9 @@
{ key: 'done', presenter: plugin.component.TodoStatePresenter, label: plugin.string.TodoState }
]}
options={{
// lookup: {
// }
sort: {
rank: 1
}
}}
query={{ attachedTo: objectId }}
/>

View File

@ -89,7 +89,7 @@ export interface Task extends AttachedDoc, DocWithRank {
/**
* @public
*/
export interface TodoItem extends AttachedDoc {
export interface TodoItem extends AttachedDoc, DocWithRank {
name: Markup
assignee: Ref<Employee> | null
done: boolean