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 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) @UX(task.string.Todo)
export class TTodoItem extends TAttachedDoc implements TodoItem { export class TTodoItem extends TAttachedDoc implements TodoItem {
@Prop(TypeMarkup(), task.string.TodoName, task.icon.Task) @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) @Prop(Collection(task.class.TodoItem), task.string.Todos)
items!: number items!: number
declare rank: string
} }
@Model(task.class.SpaceWithStates, core.class.Space) @Model(task.class.SpaceWithStates, core.class.Space)

View File

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

View File

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

View File

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

View File

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

View File

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