mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-09 09:20:54 +00:00
feat(planner): drag-n-drop (#5031)
Signed-off-by: Eduard Aksamitov <e@euaaaio.ru>
This commit is contained in:
parent
5fd60d1096
commit
9d52ddbbea
@ -50,6 +50,7 @@
|
|||||||
"@hcengineering/notification": "^0.6.16",
|
"@hcengineering/notification": "^0.6.16",
|
||||||
"@hcengineering/model-tracker": "^0.6.0",
|
"@hcengineering/model-tracker": "^0.6.0",
|
||||||
"@hcengineering/time": "^0.6.0",
|
"@hcengineering/time": "^0.6.0",
|
||||||
"@hcengineering/time-resources": "^0.6.0"
|
"@hcengineering/time-resources": "^0.6.0",
|
||||||
|
"@hcengineering/rank": "^0.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,10 +26,23 @@ import {
|
|||||||
type Space,
|
type Space,
|
||||||
type Timestamp,
|
type Timestamp,
|
||||||
type Type,
|
type Type,
|
||||||
DateRangeMode
|
DateRangeMode,
|
||||||
|
IndexKind
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import lead from '@hcengineering/lead'
|
import lead from '@hcengineering/lead'
|
||||||
import { Collection, Mixin, Model, Prop, TypeRef, TypeString, UX, type Builder, TypeDate } from '@hcengineering/model'
|
import {
|
||||||
|
Collection,
|
||||||
|
Mixin,
|
||||||
|
Model,
|
||||||
|
Prop,
|
||||||
|
TypeRef,
|
||||||
|
TypeString,
|
||||||
|
UX,
|
||||||
|
type Builder,
|
||||||
|
TypeDate,
|
||||||
|
Hidden,
|
||||||
|
Index
|
||||||
|
} from '@hcengineering/model'
|
||||||
import { TEvent } from '@hcengineering/model-calendar'
|
import { TEvent } from '@hcengineering/model-calendar'
|
||||||
import core, { TAttachedDoc, TClass, TDoc, TType } from '@hcengineering/model-core'
|
import core, { TAttachedDoc, TClass, TDoc, TType } from '@hcengineering/model-core'
|
||||||
import tracker from '@hcengineering/model-tracker'
|
import tracker from '@hcengineering/model-tracker'
|
||||||
@ -51,7 +64,8 @@ import {
|
|||||||
type WorkSlot
|
type WorkSlot
|
||||||
} from '@hcengineering/time'
|
} from '@hcengineering/time'
|
||||||
|
|
||||||
import { type Resource } from '@hcengineering/platform'
|
import type { Resource } from '@hcengineering/platform'
|
||||||
|
import type { Rank } from '@hcengineering/task'
|
||||||
import time from './plugin'
|
import time from './plugin'
|
||||||
import task from '@hcengineering/task'
|
import task from '@hcengineering/task'
|
||||||
|
|
||||||
@ -107,6 +121,10 @@ export class TToDO extends TAttachedDoc implements ToDo {
|
|||||||
|
|
||||||
@Prop(Collection(tags.class.TagReference, tags.string.TagLabel), tags.string.Tags)
|
@Prop(Collection(tags.class.TagReference, tags.string.TagLabel), tags.string.Tags)
|
||||||
labels?: number | undefined
|
labels?: number | undefined
|
||||||
|
|
||||||
|
@Index(IndexKind.Indexed)
|
||||||
|
@Hidden()
|
||||||
|
rank!: Rank
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(time.class.ProjectToDo, time.class.ToDo)
|
@Model(time.class.ProjectToDo, time.class.ToDo)
|
||||||
|
@ -14,13 +14,14 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { type PersonAccount } from '@hcengineering/contact'
|
import { type PersonAccount } from '@hcengineering/contact'
|
||||||
import { type Account, type Doc, type Ref, TxOperations } from '@hcengineering/core'
|
import { type Account, type Doc, type Ref, SortingOrder, TxOperations } from '@hcengineering/core'
|
||||||
import {
|
import {
|
||||||
type MigrateOperation,
|
type MigrateOperation,
|
||||||
type MigrationClient,
|
type MigrationClient,
|
||||||
type MigrationUpgradeClient,
|
type MigrationUpgradeClient,
|
||||||
createOrUpdate
|
createOrUpdate
|
||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
|
import { makeRank } from '@hcengineering/rank'
|
||||||
import core from '@hcengineering/model-core'
|
import core from '@hcengineering/model-core'
|
||||||
import task from '@hcengineering/task'
|
import task from '@hcengineering/task'
|
||||||
import tags from '@hcengineering/tags'
|
import tags from '@hcengineering/tags'
|
||||||
@ -37,12 +38,14 @@ export async function migrateWorkSlots (client: TxOperations): Promise<void> {
|
|||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const todos = new Map<Ref<Doc>, Ref<ToDo>>()
|
const todos = new Map<Ref<Doc>, Ref<ToDo>>()
|
||||||
const count = new Map<Ref<ToDo>, number>()
|
const count = new Map<Ref<ToDo>, number>()
|
||||||
|
let rank = makeRank(undefined, undefined)
|
||||||
for (const oldWorkSlot of oldWorkSlots) {
|
for (const oldWorkSlot of oldWorkSlots) {
|
||||||
const todo = todos.get(oldWorkSlot.attachedTo)
|
const todo = todos.get(oldWorkSlot.attachedTo)
|
||||||
if (todo === undefined) {
|
if (todo === undefined) {
|
||||||
const acc = oldWorkSlot.space.replace('_calendar', '') as Ref<Account>
|
const acc = oldWorkSlot.space.replace('_calendar', '') as Ref<Account>
|
||||||
const account = (await client.findOne(core.class.Account, { _id: acc })) as PersonAccount
|
const account = (await client.findOne(core.class.Account, { _id: acc })) as PersonAccount
|
||||||
if (account.person !== undefined) {
|
if (account.person !== undefined) {
|
||||||
|
rank = makeRank(undefined, rank)
|
||||||
const todo = await client.addCollection(
|
const todo = await client.addCollection(
|
||||||
time.class.ProjectToDo,
|
time.class.ProjectToDo,
|
||||||
time.space.ToDos,
|
time.space.ToDos,
|
||||||
@ -57,7 +60,8 @@ export async function migrateWorkSlots (client: TxOperations): Promise<void> {
|
|||||||
workslots: 0,
|
workslots: 0,
|
||||||
priority: ToDoPriority.NoPriority,
|
priority: ToDoPriority.NoPriority,
|
||||||
user: account.person,
|
user: account.person,
|
||||||
visibility: 'public'
|
visibility: 'public',
|
||||||
|
rank
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await client.update(oldWorkSlot, {
|
await client.update(oldWorkSlot, {
|
||||||
@ -105,6 +109,44 @@ async function migrateTodosSpace (client: TxOperations): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function migrateTodosRanks (client: TxOperations): Promise<void> {
|
||||||
|
const doneTodos = await client.findAll(
|
||||||
|
time.class.ToDo,
|
||||||
|
{
|
||||||
|
rank: { $exists: false },
|
||||||
|
doneOn: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sort: { modifiedOn: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
let doneTodoRank = makeRank(undefined, undefined)
|
||||||
|
for (const todo of doneTodos) {
|
||||||
|
await client.update(todo, {
|
||||||
|
rank: doneTodoRank
|
||||||
|
})
|
||||||
|
doneTodoRank = makeRank(undefined, doneTodoRank)
|
||||||
|
}
|
||||||
|
|
||||||
|
const undoneTodos = await client.findAll(
|
||||||
|
time.class.ToDo,
|
||||||
|
{
|
||||||
|
rank: { $exists: false },
|
||||||
|
doneOn: { $ne: null }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sort: { doneOn: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
let undoneTodoRank = makeRank(undefined, undefined)
|
||||||
|
for (const todo of undoneTodos) {
|
||||||
|
await client.update(todo, {
|
||||||
|
rank: undoneTodoRank
|
||||||
|
})
|
||||||
|
undoneTodoRank = makeRank(undefined, undoneTodoRank)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function createDefaultSpace (tx: TxOperations): Promise<void> {
|
async function createDefaultSpace (tx: TxOperations): Promise<void> {
|
||||||
const current = await tx.findOne(core.class.Space, {
|
const current = await tx.findOne(core.class.Space, {
|
||||||
_id: time.space.ToDos
|
_id: time.space.ToDos
|
||||||
@ -161,5 +203,6 @@ export const timeOperation: MigrateOperation = {
|
|||||||
)
|
)
|
||||||
await migrateWorkSlots(tx)
|
await migrateWorkSlots(tx)
|
||||||
await migrateTodosSpace(tx)
|
await migrateTodosSpace(tx)
|
||||||
|
await migrateTodosRanks(tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -457,7 +457,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.large {
|
&.large {
|
||||||
padding: var(--spacing-2) var(--spacing-1_5) var(--spacing-1) var(--spacing-2);
|
padding: var(--spacing-2) var(--spacing-1_5) var(--spacing-2) var(--spacing-2);
|
||||||
min-height: var(--global-extra-large-Size);
|
min-height: var(--global-extra-large-Size);
|
||||||
|
|
||||||
.hulyAccordionItem-header__label-wrapper {
|
.hulyAccordionItem-header__label-wrapper {
|
||||||
@ -526,6 +526,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hulyToDoLine-draggable {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.is-dragging-over-up::before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
inset: 0;
|
||||||
|
border-top: 1px solid var(--global-focus-BorderColor);
|
||||||
|
}
|
||||||
|
&.is-dragging-over-down::before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
inset: 0;
|
||||||
|
border-bottom: 1px solid var(--global-focus-BorderColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ToDo Line */
|
/* ToDo Line */
|
||||||
.hulyToDoLine-container {
|
.hulyToDoLine-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -567,6 +584,7 @@
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
cursor: grab;
|
||||||
|
|
||||||
&.isNew::after {
|
&.isNew::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
"@hcengineering/workbench": "^0.6.9",
|
"@hcengineering/workbench": "^0.6.9",
|
||||||
"@hcengineering/document": "^0.6.0",
|
"@hcengineering/document": "^0.6.0",
|
||||||
"@hcengineering/time": "^0.6.0",
|
"@hcengineering/time": "^0.6.0",
|
||||||
|
"@hcengineering/rank": "^0.6.0",
|
||||||
"@tiptap/core": "^2.1.12",
|
"@tiptap/core": "^2.1.12",
|
||||||
"slugify": "^1.6.6"
|
"slugify": "^1.6.6"
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Person } from '@hcengineering/contact'
|
import { Person } from '@hcengineering/contact'
|
||||||
import { AssigneePopup, EmployeePresenter } from '@hcengineering/contact-resources'
|
import { AssigneePopup, EmployeePresenter } from '@hcengineering/contact-resources'
|
||||||
import { Doc, Ref } from '@hcengineering/core'
|
import { Doc, Ref, SortingOrder } from '@hcengineering/core'
|
||||||
import { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
|
import { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { NodeViewContent, NodeViewProps, NodeViewWrapper } from '@hcengineering/text-editor'
|
import { NodeViewContent, NodeViewProps, NodeViewWrapper } from '@hcengineering/text-editor'
|
||||||
import { CheckBox, getEventPositionElement, showPopup } from '@hcengineering/ui'
|
import { CheckBox, getEventPositionElement, showPopup } from '@hcengineering/ui'
|
||||||
import time, { ToDo, ToDoPriority } from '@hcengineering/time'
|
import time, { ToDo, ToDoPriority } from '@hcengineering/time'
|
||||||
|
import { makeRank } from '@hcengineering/rank'
|
||||||
|
|
||||||
import document from '../../plugin'
|
import document from '../../plugin'
|
||||||
|
|
||||||
@ -75,6 +76,20 @@
|
|||||||
await ops.remove(todo)
|
await ops.remove(todo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const doneOn = node.attrs.checked === true ? Date.now() : null
|
||||||
|
|
||||||
|
const latestTodoItem = await ops.findOne(
|
||||||
|
time.class.ToDo,
|
||||||
|
{
|
||||||
|
user,
|
||||||
|
doneOn: doneOn === null ? null : { $ne: null }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sort: { rank: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const rank = makeRank(undefined, latestTodoItem?.rank)
|
||||||
|
|
||||||
const id = await ops.addCollection(time.class.ProjectToDo, time.space.ToDos, object._id, object._class, 'todos', {
|
const id = await ops.addCollection(time.class.ProjectToDo, time.space.ToDos, object._id, object._class, 'todos', {
|
||||||
attachedSpace: object.space,
|
attachedSpace: object.space,
|
||||||
title,
|
title,
|
||||||
@ -83,7 +98,8 @@
|
|||||||
workslots: 0,
|
workslots: 0,
|
||||||
priority: ToDoPriority.NoPriority,
|
priority: ToDoPriority.NoPriority,
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
doneOn: node.attrs.checked === true ? Date.now() : null
|
doneOn,
|
||||||
|
rank
|
||||||
})
|
})
|
||||||
|
|
||||||
await ops.commit()
|
await ops.commit()
|
||||||
|
@ -1,30 +1,45 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { ActionIcon, IconAdd, showPopup, ModernEditbox } from '@hcengineering/ui'
|
||||||
|
import { SortingOrder, getCurrentAccount } from '@hcengineering/core'
|
||||||
import { PersonAccount } from '@hcengineering/contact'
|
import { PersonAccount } from '@hcengineering/contact'
|
||||||
import { getCurrentAccount } from '@hcengineering/core'
|
|
||||||
import { getClient } from '@hcengineering/presentation'
|
|
||||||
import { ActionIcon, EditBox, IconAdd, showPopup, ModernEditbox } from '@hcengineering/ui'
|
|
||||||
import { ToDoPriority } from '@hcengineering/time'
|
import { ToDoPriority } from '@hcengineering/time'
|
||||||
import time from '../plugin'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import CreateToDoPopup from './CreateToDoPopup.svelte'
|
import CreateToDoPopup from './CreateToDoPopup.svelte'
|
||||||
|
import time from '../plugin'
|
||||||
|
import { makeRank } from '@hcengineering/task'
|
||||||
|
|
||||||
export let fullSize: boolean = false
|
export let fullSize: boolean = false
|
||||||
let value: string = ''
|
let value: string = ''
|
||||||
|
|
||||||
async function save () {
|
const client = getClient()
|
||||||
|
const acc = getCurrentAccount() as PersonAccount
|
||||||
|
|
||||||
|
async function save (): Promise<void> {
|
||||||
let [name, description] = value.split('//')
|
let [name, description] = value.split('//')
|
||||||
name = name.trim()
|
name = name.trim()
|
||||||
if (name.length === 0) return
|
if (name.length === 0) return
|
||||||
description = description?.trim() ?? ''
|
description = description?.trim() ?? ''
|
||||||
const client = getClient()
|
const ops = client.apply('todo')
|
||||||
const acc = getCurrentAccount() as PersonAccount
|
const latestTodo = await ops.findOne(
|
||||||
await client.addCollection(time.class.ToDo, time.space.ToDos, time.ids.NotAttached, time.class.ToDo, 'todos', {
|
time.class.ToDo,
|
||||||
|
{
|
||||||
|
user: acc.person,
|
||||||
|
doneOn: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sort: { rank: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await ops.addCollection(time.class.ToDo, time.space.ToDos, time.ids.NotAttached, time.class.ToDo, 'todos', {
|
||||||
title: name,
|
title: name,
|
||||||
description,
|
description,
|
||||||
user: acc.person,
|
user: acc.person,
|
||||||
workslots: 0,
|
workslots: 0,
|
||||||
priority: ToDoPriority.NoPriority,
|
priority: ToDoPriority.NoPriority,
|
||||||
visibility: 'private'
|
visibility: 'private',
|
||||||
|
rank: makeRank(undefined, latestTodo?.rank)
|
||||||
})
|
})
|
||||||
|
await ops.commit()
|
||||||
clear()
|
clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { AttachedData, Doc, Ref, generateId, getCurrentAccount } from '@hcengineering/core'
|
import core, { AttachedData, Doc, Ref, SortingOrder, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||||
import { Button, Component, EditBox, IconClose, Label, Scroller } from '@hcengineering/ui'
|
import { Button, Component, EditBox, IconClose, Label, Scroller } from '@hcengineering/ui'
|
||||||
import { SpaceSelector, createQuery, getClient } from '@hcengineering/presentation'
|
import { SpaceSelector, createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { ToDo, ToDoPriority, WorkSlot } from '@hcengineering/time'
|
import { ToDo, ToDoPriority, WorkSlot } from '@hcengineering/time'
|
||||||
@ -24,7 +24,7 @@
|
|||||||
import { StyledTextBox } from '@hcengineering/text-editor'
|
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||||
import { PersonAccount } from '@hcengineering/contact'
|
import { PersonAccount } from '@hcengineering/contact'
|
||||||
import calendar from '@hcengineering/calendar-resources/src/plugin'
|
import calendar from '@hcengineering/calendar-resources/src/plugin'
|
||||||
import task from '@hcengineering/task'
|
import task, { makeRank } from '@hcengineering/task'
|
||||||
import PriorityEditor from './PriorityEditor.svelte'
|
import PriorityEditor from './PriorityEditor.svelte'
|
||||||
import DueDateEditor from './DueDateEditor.svelte'
|
import DueDateEditor from './DueDateEditor.svelte'
|
||||||
import Workslots from './Workslots.svelte'
|
import Workslots from './Workslots.svelte'
|
||||||
@ -40,7 +40,8 @@
|
|||||||
priority: ToDoPriority.NoPriority,
|
priority: ToDoPriority.NoPriority,
|
||||||
attachedSpace: object?.space,
|
attachedSpace: object?.space,
|
||||||
visibility: 'private',
|
visibility: 'private',
|
||||||
user: acc.person
|
user: acc.person,
|
||||||
|
rank: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
@ -54,7 +55,18 @@
|
|||||||
|
|
||||||
async function saveToDo (): Promise<void> {
|
async function saveToDo (): Promise<void> {
|
||||||
loading = true
|
loading = true
|
||||||
const id = await client.addCollection(
|
const ops = client.apply('todo')
|
||||||
|
const latestTodo = await ops.findOne(
|
||||||
|
time.class.ToDo,
|
||||||
|
{
|
||||||
|
user: acc.person,
|
||||||
|
doneOn: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sort: { rank: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const id = await ops.addCollection(
|
||||||
time.class.ToDo,
|
time.class.ToDo,
|
||||||
time.space.ToDos,
|
time.space.ToDos,
|
||||||
object?._id ?? time.ids.NotAttached,
|
object?._id ?? time.ids.NotAttached,
|
||||||
@ -68,12 +80,13 @@
|
|||||||
visibility: todo.visibility,
|
visibility: todo.visibility,
|
||||||
user: acc.person,
|
user: acc.person,
|
||||||
dueDate: todo.dueDate,
|
dueDate: todo.dueDate,
|
||||||
attachedSpace: todo.attachedSpace
|
attachedSpace: todo.attachedSpace,
|
||||||
|
rank: makeRank(undefined, latestTodo?.rank)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const space = `${acc._id}_calendar` as Ref<Calendar>
|
const space = `${acc._id}_calendar` as Ref<Calendar>
|
||||||
for (const slot of slots) {
|
for (const slot of slots) {
|
||||||
await client.addCollection(time.class.WorkSlot, space, id, time.class.ToDo, 'workslots', {
|
await ops.addCollection(time.class.WorkSlot, space, id, time.class.ToDo, 'workslots', {
|
||||||
eventId: generateEventId(),
|
eventId: generateEventId(),
|
||||||
date: slot.date,
|
date: slot.date,
|
||||||
dueDate: slot.dueDate,
|
dueDate: slot.dueDate,
|
||||||
@ -87,8 +100,9 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
await client.addCollection(tagsPlugin.class.TagReference, time.space.ToDos, id, time.class.ToDo, 'labels', tag)
|
await ops.addCollection(tagsPlugin.class.TagReference, time.space.ToDos, id, time.class.ToDo, 'labels', tag)
|
||||||
}
|
}
|
||||||
|
await ops.commit()
|
||||||
dispatch('close', true)
|
dispatch('close', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, afterUpdate } from 'svelte'
|
import { createEventDispatcher, afterUpdate } from 'svelte'
|
||||||
import calendar, { Calendar, generateEventId } from '@hcengineering/calendar'
|
import calendar, { Calendar, generateEventId } from '@hcengineering/calendar'
|
||||||
@ -6,13 +21,13 @@
|
|||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { TagElement } from '@hcengineering/tags'
|
import { TagElement } from '@hcengineering/tags'
|
||||||
import { Separator, defineSeparators } from '@hcengineering/ui'
|
import { Separator, defineSeparators } from '@hcengineering/ui'
|
||||||
import { ToDo } from '@hcengineering/time'
|
|
||||||
import { ToDosMode } from '..'
|
import { ToDosMode } from '..'
|
||||||
import time from '../plugin'
|
|
||||||
import { timeSeparators } from '../utils'
|
|
||||||
import PlanningCalendar from './PlanningCalendar.svelte'
|
import PlanningCalendar from './PlanningCalendar.svelte'
|
||||||
import ToDos from './ToDos.svelte'
|
|
||||||
import ToDosNavigator from './ToDosNavigator.svelte'
|
import ToDosNavigator from './ToDosNavigator.svelte'
|
||||||
|
import ToDos from './ToDos.svelte'
|
||||||
|
import { timeSeparators } from '../utils'
|
||||||
|
import { dragging } from '../dragging'
|
||||||
|
import time from '../plugin'
|
||||||
|
|
||||||
export let visibleNav: boolean = true
|
export let visibleNav: boolean = true
|
||||||
export let navFloat: boolean = false
|
export let navFloat: boolean = false
|
||||||
@ -26,12 +41,12 @@
|
|||||||
|
|
||||||
let currentDate: Date = new Date()
|
let currentDate: Date = new Date()
|
||||||
|
|
||||||
let dragItem: ToDo | undefined = undefined
|
$: dragItem = $dragging.item
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
async function drop (e: CustomEvent<any>) {
|
async function drop (e: CustomEvent<any>) {
|
||||||
if (dragItem === undefined) return
|
if (dragItem === null) return
|
||||||
const doc = dragItem
|
const doc = dragItem
|
||||||
const date = e.detail.date.getTime()
|
const date = e.detail.date.getTime()
|
||||||
const currentUser = getCurrentAccount() as PersonAccount
|
const currentUser = getCurrentAccount() as PersonAccount
|
||||||
@ -79,14 +94,7 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex-col clear-mins">
|
<div class="flex-col clear-mins">
|
||||||
<ToDos
|
<ToDos {mode} {tag} bind:isVisiblePlannerNav bind:currentDate />
|
||||||
{mode}
|
|
||||||
{tag}
|
|
||||||
bind:isVisiblePlannerNav
|
|
||||||
bind:currentDate
|
|
||||||
on:dragstart={(e) => (dragItem = e.detail)}
|
|
||||||
on:dragend={() => (dragItem = undefined)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<Separator name={'time'} float={navFloat} index={1} color={'transparent'} separatorSize={0} short />
|
<Separator name={'time'} float={navFloat} index={1} color={'transparent'} separatorSize={0} short />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
import time from '../plugin'
|
import time from '../plugin'
|
||||||
import IconSun from './icons/Sun.svelte'
|
import IconSun from './icons/Sun.svelte'
|
||||||
|
|
||||||
export let dragItem: ToDo | undefined = undefined
|
export let dragItem: ToDo | null = null
|
||||||
export let currentDate: Date = new Date()
|
export let currentDate: Date = new Date()
|
||||||
export let displayedDaysCount = 1
|
export let displayedDaysCount = 1
|
||||||
export let createComponent: AnyComponent | undefined = calendar.component.CreateEvent
|
export let createComponent: AnyComponent | undefined = calendar.component.CreateEvent
|
||||||
@ -151,8 +151,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear (dragItem: ToDo | undefined) {
|
function clear (dragItem: ToDo | null) {
|
||||||
if (dragItem === undefined) {
|
if (dragItem === null) {
|
||||||
raw = raw.filter((p) => p._id !== dragItemId)
|
raw = raw.filter((p) => p._id !== dragItemId)
|
||||||
all = getAllEvents(raw, from, to)
|
all = getAllEvents(raw, from, to)
|
||||||
objects = hidePrivateEvents(all, $calendarStore)
|
objects = hidePrivateEvents(all, $calendarStore)
|
||||||
@ -216,7 +216,7 @@
|
|||||||
events={objects}
|
events={objects}
|
||||||
{displayedDaysCount}
|
{displayedDaysCount}
|
||||||
startFromWeekStart={false}
|
startFromWeekStart={false}
|
||||||
clearCells={dragItem !== undefined}
|
clearCells={dragItem !== null}
|
||||||
{dragItemId}
|
{dragItemId}
|
||||||
on:dragEnter={dragEnter}
|
on:dragEnter={dragEnter}
|
||||||
on:dragleave={dragLeave}
|
on:dragleave={dragLeave}
|
||||||
|
113
plugins/time-resources/src/components/ToDoDraggable.svelte
Normal file
113
plugins/time-resources/src/components/ToDoDraggable.svelte
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
|
import type { WithLookup } from '@hcengineering/core'
|
||||||
|
import type { ToDo } from '@hcengineering/time'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { dragging } from '../dragging'
|
||||||
|
import time from '../plugin'
|
||||||
|
|
||||||
|
export let todo: WithLookup<ToDo>
|
||||||
|
export let index: number
|
||||||
|
export let groupName: IntlString | null
|
||||||
|
export let projectId: string | false | null
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
let isDragging: boolean = false
|
||||||
|
let draggingOverClass: string = ''
|
||||||
|
|
||||||
|
$: draggingItemIndex = $dragging.itemIndex
|
||||||
|
$: draggingGroupName = $dragging.groupName
|
||||||
|
$: draggingProjectId = $dragging.projectId
|
||||||
|
$: draggingOverIndex = $dragging.overItemIndex
|
||||||
|
$: draggingOverGroupName = $dragging.overGroupName
|
||||||
|
$: draggingOverProjectId = $dragging.overProjectId
|
||||||
|
|
||||||
|
$: isDraggable = groupName !== time.string.Done
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (
|
||||||
|
isDraggable &&
|
||||||
|
draggingItemIndex !== null &&
|
||||||
|
draggingOverGroupName === groupName &&
|
||||||
|
draggingGroupName === groupName &&
|
||||||
|
draggingOverProjectId === projectId &&
|
||||||
|
draggingProjectId === projectId &&
|
||||||
|
index === draggingOverIndex
|
||||||
|
) {
|
||||||
|
draggingOverClass = index < draggingItemIndex ? 'is-dragging-over-up' : 'is-dragging-over-down'
|
||||||
|
} else {
|
||||||
|
draggingOverClass = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragStart (event: DragEvent): void {
|
||||||
|
if (event.dataTransfer) {
|
||||||
|
event.dataTransfer.effectAllowed = 'all'
|
||||||
|
}
|
||||||
|
isDragging = true
|
||||||
|
dragging.update((state) => ({
|
||||||
|
...state,
|
||||||
|
item: todo,
|
||||||
|
itemIndex: index,
|
||||||
|
groupName,
|
||||||
|
projectId
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragEnd (): void {
|
||||||
|
isDragging = false
|
||||||
|
dragging.set({
|
||||||
|
item: null,
|
||||||
|
itemIndex: null,
|
||||||
|
groupName: null,
|
||||||
|
projectId: null,
|
||||||
|
overItemIndex: null,
|
||||||
|
overGroupName: null,
|
||||||
|
overProjectId: null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragOver (): void {
|
||||||
|
if (!isDraggable) return
|
||||||
|
dragging.update((state) => ({
|
||||||
|
...state,
|
||||||
|
overItemIndex: index,
|
||||||
|
overGroupName: groupName,
|
||||||
|
overProjectId: projectId
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDrop (event: DragEvent): void {
|
||||||
|
if (!isDraggable) return
|
||||||
|
dispatch('drop', { event, index })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<div
|
||||||
|
class="hulyToDoLine-draggable {draggingOverClass} step-tb125"
|
||||||
|
class:dragging={isDragging}
|
||||||
|
draggable={true}
|
||||||
|
on:dragstart={handleDragStart}
|
||||||
|
on:dragend={handleDragEnd}
|
||||||
|
on:dragover|preventDefault={handleDragOver}
|
||||||
|
on:drop={handleDrop}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
@ -1,3 +1,17 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { SortingOrder, WithLookup } from '@hcengineering/core'
|
import { SortingOrder, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
@ -5,7 +19,6 @@
|
|||||||
import { Component, IconMoreV2, Spinner, showPanel, Icon } from '@hcengineering/ui'
|
import { Component, IconMoreV2, Spinner, showPanel, Icon } from '@hcengineering/ui'
|
||||||
import { showMenu } from '@hcengineering/view-resources'
|
import { showMenu } from '@hcengineering/view-resources'
|
||||||
import time, { ToDo, ToDoPriority, WorkSlot } from '@hcengineering/time'
|
import time, { ToDo, ToDoPriority, WorkSlot } from '@hcengineering/time'
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import plugin from '../plugin'
|
import plugin from '../plugin'
|
||||||
import ToDoDuration from './ToDoDuration.svelte'
|
import ToDoDuration from './ToDoDuration.svelte'
|
||||||
import WorkItemPresenter from './WorkItemPresenter.svelte'
|
import WorkItemPresenter from './WorkItemPresenter.svelte'
|
||||||
@ -15,13 +28,10 @@
|
|||||||
export let todo: WithLookup<ToDo>
|
export let todo: WithLookup<ToDo>
|
||||||
export let size: 'small' | 'large' = 'small'
|
export let size: 'small' | 'large' = 'small'
|
||||||
export let planned: boolean = true
|
export let planned: boolean = true
|
||||||
export let draggable: boolean = true
|
|
||||||
export let isNew: boolean = false
|
export let isNew: boolean = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
let updating: Promise<any> | undefined = undefined
|
let updating: Promise<any> | undefined = undefined
|
||||||
let isDrag: boolean = false
|
|
||||||
|
|
||||||
async function markDone (): Promise<void> {
|
async function markDone (): Promise<void> {
|
||||||
await updating
|
await updating
|
||||||
@ -51,17 +61,6 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function dragStart (todo: ToDo, event: DragEvent & { currentTarget: EventTarget & HTMLButtonElement }): void {
|
|
||||||
event.currentTarget.classList.add('dragged')
|
|
||||||
if (event.dataTransfer) event.dataTransfer.effectAllowed = 'all'
|
|
||||||
dispatch('dragstart', todo)
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragEnd (event: DragEvent & { currentTarget: EventTarget & HTMLButtonElement }): void {
|
|
||||||
event.currentTarget.classList.remove('dragged')
|
|
||||||
dispatch('dragend')
|
|
||||||
}
|
|
||||||
|
|
||||||
function open (e: MouseEvent): void {
|
function open (e: MouseEvent): void {
|
||||||
showPanel(time.component.EditToDo, todo._id, todo._class, 'content')
|
showPanel(time.component.EditToDo, todo._id, todo._class, 'content')
|
||||||
}
|
}
|
||||||
@ -73,20 +72,10 @@
|
|||||||
class="hulyToDoLine-container {size}"
|
class="hulyToDoLine-container {size}"
|
||||||
class:hovered
|
class:hovered
|
||||||
class:isDone
|
class:isDone
|
||||||
class:isDrag
|
|
||||||
on:click|stopPropagation={open}
|
on:click|stopPropagation={open}
|
||||||
on:contextmenu={(e) => {
|
on:contextmenu={(e) => {
|
||||||
showMenu(e, { object: todo })
|
showMenu(e, { object: todo })
|
||||||
}}
|
}}
|
||||||
{draggable}
|
|
||||||
on:dragstart={(e) => {
|
|
||||||
isDrag = true
|
|
||||||
dragStart(todo, e)
|
|
||||||
}}
|
|
||||||
on:dragend={(e) => {
|
|
||||||
isDrag = false
|
|
||||||
dragEnd(e)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div class="flex-row-top flex-grow flex-gap-2">
|
<div class="flex-row-top flex-grow flex-gap-2">
|
||||||
<div class="flex-row-center flex-no-shrink">
|
<div class="flex-row-center flex-no-shrink">
|
||||||
|
@ -1,21 +1,33 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { WithLookup, IdMap, Ref, Space } from '@hcengineering/core'
|
import type { WithLookup, IdMap, Ref, Space } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import type { ToDo, WorkSlot } from '@hcengineering/time'
|
||||||
import { ToDo, WorkSlot } from '@hcengineering/time'
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
import time from '../plugin'
|
import type { Project } from '@hcengineering/tracker'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import type { ToDosMode } from '..'
|
||||||
|
import { AccordionItem } from '@hcengineering/ui'
|
||||||
|
import ToDoDraggable from './ToDoDraggable.svelte'
|
||||||
import ToDoDuration from './ToDoDuration.svelte'
|
import ToDoDuration from './ToDoDuration.svelte'
|
||||||
import ToDoElement from './ToDoElement.svelte'
|
import ToDoElement from './ToDoElement.svelte'
|
||||||
import {
|
import time from '../plugin'
|
||||||
AccordionItem,
|
import { dragging } from '../dragging'
|
||||||
IconWithEmoji,
|
import ToDoProjectGroup from './ToDoProjectGroup.svelte'
|
||||||
getPlatformColorDef,
|
import { getClient } from '@hcengineering/presentation'
|
||||||
getPlatformColorForTextDef,
|
import { makeRank } from '@hcengineering/task'
|
||||||
themeStore
|
|
||||||
} from '@hcengineering/ui'
|
|
||||||
import { ToDosMode } from '..'
|
|
||||||
import tracker, { Project } from '@hcengineering/tracker'
|
|
||||||
import view from '@hcengineering/view'
|
|
||||||
|
|
||||||
export let mode: ToDosMode
|
export let mode: ToDosMode
|
||||||
export let title: IntlString
|
export let title: IntlString
|
||||||
@ -25,8 +37,6 @@
|
|||||||
export let largeSize: boolean = false
|
export let largeSize: boolean = false
|
||||||
export let projects: IdMap<Project>
|
export let projects: IdMap<Project>
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
function getAllWorkslots (todos: WithLookup<ToDo>[]): WorkSlot[] {
|
function getAllWorkslots (todos: WithLookup<ToDo>[]): WorkSlot[] {
|
||||||
const workslots: WorkSlot[] = []
|
const workslots: WorkSlot[] = []
|
||||||
for (const todo of todos) {
|
for (const todo of todos) {
|
||||||
@ -56,9 +66,28 @@
|
|||||||
withoutProject = wp
|
withoutProject = wp
|
||||||
return _groups
|
return _groups
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasProject = (proj: Ref<Space> | undefined): boolean => {
|
const hasProject = (proj: Ref<Space> | undefined): boolean => {
|
||||||
return (proj && projects.has(proj as Ref<Project>)) ?? false
|
return (proj && projects.has(proj as Ref<Project>)) ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
$: draggingItem = $dragging.item
|
||||||
|
$: draggingItemIndex = $dragging.itemIndex
|
||||||
|
|
||||||
|
async function handleDrop (event: CustomEvent<{ event: DragEvent, index: number }>): Promise<void> {
|
||||||
|
if (draggingItem === null || draggingItemIndex === null) return
|
||||||
|
|
||||||
|
const droppingIndex = event.detail.index
|
||||||
|
const [previousItem, nextItem] = [
|
||||||
|
todos[draggingItemIndex < droppingIndex ? droppingIndex : droppingIndex - 1],
|
||||||
|
todos[draggingItemIndex < droppingIndex ? droppingIndex + 1 : droppingIndex]
|
||||||
|
]
|
||||||
|
|
||||||
|
const newRank = makeRank(previousItem?.rank, nextItem?.rank)
|
||||||
|
await client.update(draggingItem, { rank: newRank })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if showTitle}
|
{#if showTitle}
|
||||||
@ -68,7 +97,7 @@
|
|||||||
bottomSpace={false}
|
bottomSpace={false}
|
||||||
counter={todos.length}
|
counter={todos.length}
|
||||||
duration={showDuration}
|
duration={showDuration}
|
||||||
isOpen
|
isOpen={title !== time.string.Done}
|
||||||
fixHeader
|
fixHeader
|
||||||
background={'var(--theme-navpanel-color)'}
|
background={'var(--theme-navpanel-color)'}
|
||||||
>
|
>
|
||||||
@ -77,48 +106,31 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
{#if groups}
|
{#if groups}
|
||||||
{#each groups as group}
|
{#each groups as group}
|
||||||
<AccordionItem
|
<ToDoProjectGroup
|
||||||
icon={group.icon === view.ids.IconWithEmoji ? IconWithEmoji : group.icon ?? tracker.icon.Home}
|
todos={todos.filter((td) => td.attachedSpace === group._id)}
|
||||||
iconProps={group.icon === view.ids.IconWithEmoji
|
project={group}
|
||||||
? { icon: group.color }
|
groupName={title}
|
||||||
: {
|
{largeSize}
|
||||||
fill:
|
{mode}
|
||||||
group.color !== undefined
|
/>
|
||||||
? getPlatformColorDef(group.color, $themeStore.dark).icon
|
|
||||||
: getPlatformColorForTextDef(group.name, $themeStore.dark).icon
|
|
||||||
}}
|
|
||||||
title={group.name}
|
|
||||||
size={'medium'}
|
|
||||||
isOpen
|
|
||||||
nested
|
|
||||||
>
|
|
||||||
{#each todos.filter((td) => td.attachedSpace === group._id) as todo}
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<div class="step-tb125" draggable={true} on:dragend on:dragstart={() => dispatch('dragstart', todo)}>
|
|
||||||
<ToDoElement {todo} size={largeSize ? 'large' : 'small'} planned={mode !== 'unplanned'} />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</AccordionItem>
|
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
{#if withoutProject}
|
{#if withoutProject}
|
||||||
<AccordionItem label={time.string.WithoutProject} size={'medium'} isOpen nested>
|
<ToDoProjectGroup
|
||||||
{#each todos.filter((td) => !hasProject(td.attachedSpace)) as todo}
|
todos={todos.filter((td) => !hasProject(td.attachedSpace))}
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
project={false}
|
||||||
<div class="step-tb125" draggable={true} on:dragend on:dragstart={() => dispatch('dragstart', todo)}>
|
groupName={title}
|
||||||
<ToDoElement {todo} size={largeSize ? 'large' : 'small'} planned={mode !== 'unplanned'} />
|
{largeSize}
|
||||||
</div>
|
{mode}
|
||||||
{/each}
|
/>
|
||||||
</AccordionItem>
|
|
||||||
{/if}
|
{/if}
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex-col p-4 w-full">
|
<div class="flex-col p-4 w-full">
|
||||||
{#each todos as todo}
|
{#each todos as todo, index}
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<ToDoDraggable {todo} {index} groupName={title} projectId={false} on:drop={handleDrop}>
|
||||||
<div class="step-tb125" draggable={true} on:dragend on:dragstart={() => dispatch('dragstart', todo)}>
|
|
||||||
<ToDoElement {todo} size={largeSize ? 'large' : 'small'} planned={mode !== 'unplanned'} />
|
<ToDoElement {todo} size={largeSize ? 'large' : 'small'} planned={mode !== 'unplanned'} />
|
||||||
</div>
|
</ToDoDraggable>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
|
import type { Project } from '@hcengineering/tracker'
|
||||||
|
import type { ToDo } from '@hcengineering/time'
|
||||||
|
import type { ToDosMode } from '..'
|
||||||
|
import {
|
||||||
|
AccordionItem,
|
||||||
|
IconWithEmoji,
|
||||||
|
getPlatformColorDef,
|
||||||
|
getPlatformColorForTextDef,
|
||||||
|
themeStore
|
||||||
|
} from '@hcengineering/ui'
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import { makeRank } from '@hcengineering/task'
|
||||||
|
import tracker from '@hcengineering/tracker'
|
||||||
|
import view from '@hcengineering/view'
|
||||||
|
import ToDoDraggable from './ToDoDraggable.svelte'
|
||||||
|
import ToDoElement from './ToDoElement.svelte'
|
||||||
|
import time from '../plugin'
|
||||||
|
import { dragging } from '../dragging'
|
||||||
|
|
||||||
|
export let todos: ToDo[]
|
||||||
|
export let project: Project | false | undefined = undefined
|
||||||
|
export let mode: ToDosMode
|
||||||
|
export let groupName: IntlString
|
||||||
|
export let largeSize: boolean = false
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
let projectId: string | false
|
||||||
|
|
||||||
|
$: icon = project
|
||||||
|
? project.icon === view.ids.IconWithEmoji
|
||||||
|
? IconWithEmoji
|
||||||
|
: project.icon ?? tracker.icon.Home
|
||||||
|
: undefined
|
||||||
|
$: iconProps = project
|
||||||
|
? project.icon === view.ids.IconWithEmoji
|
||||||
|
? { icon: project.color }
|
||||||
|
: {
|
||||||
|
fill:
|
||||||
|
project.color !== undefined
|
||||||
|
? getPlatformColorDef(project.color, $themeStore.dark).icon
|
||||||
|
: getPlatformColorForTextDef(project.name, $themeStore.dark).icon
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
$: title = project ? project.name : undefined
|
||||||
|
$: label = title ? undefined : time.string.WithoutProject
|
||||||
|
$: projectId = project ? project._id : false
|
||||||
|
|
||||||
|
$: draggingItem = $dragging.item
|
||||||
|
$: draggingItemIndex = $dragging.itemIndex
|
||||||
|
|
||||||
|
async function handleDrop (event: CustomEvent<{ event: DragEvent, index: number }>): Promise<void> {
|
||||||
|
if (draggingItem === null || draggingItemIndex === null) return
|
||||||
|
|
||||||
|
const droppingIndex = event.detail.index
|
||||||
|
|
||||||
|
const [previousItem, nextItem] = [
|
||||||
|
todos[draggingItemIndex < droppingIndex ? droppingIndex : droppingIndex - 1],
|
||||||
|
todos[draggingItemIndex < droppingIndex ? droppingIndex + 1 : droppingIndex]
|
||||||
|
]
|
||||||
|
|
||||||
|
const newRank = makeRank(previousItem?.rank, nextItem?.rank)
|
||||||
|
await client.update(draggingItem, { rank: newRank })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AccordionItem {icon} {iconProps} {title} {label} size={'medium'} isOpen nested>
|
||||||
|
{#each todos as todo, index}
|
||||||
|
<ToDoDraggable {todo} {index} {groupName} {projectId} on:drop={handleDrop}>
|
||||||
|
<ToDoElement {todo} size={largeSize ? 'large' : 'small'} planned={mode !== 'unplanned'} />
|
||||||
|
</ToDoDraggable>
|
||||||
|
{/each}
|
||||||
|
</AccordionItem>
|
@ -1,21 +1,39 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PersonAccount } from '@hcengineering/contact'
|
import type { DocumentQuery, Ref, WithLookup, IdMap } from '@hcengineering/core'
|
||||||
import { DocumentQuery, Ref, SortingOrder, WithLookup, getCurrentAccount, IdMap, toIdMap } from '@hcengineering/core'
|
import type { ToDo, WorkSlot } from '@hcengineering/time'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import type { PersonAccount } from '@hcengineering/contact'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
|
import type { TagElement } from '@hcengineering/tags'
|
||||||
|
import type { Project } from '@hcengineering/tracker'
|
||||||
|
import type { ToDosMode } from '..'
|
||||||
import { Scroller, areDatesEqual, todosSP, defaultSP, Header, ButtonIcon, Label } from '@hcengineering/ui'
|
import { Scroller, areDatesEqual, todosSP, defaultSP, Header, ButtonIcon, Label } from '@hcengineering/ui'
|
||||||
import { ToDo, WorkSlot } from '@hcengineering/time'
|
import { getCurrentAccount, toIdMap, SortingOrder } from '@hcengineering/core'
|
||||||
import { ToDosMode } from '..'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
|
import tracker from '@hcengineering/tracker'
|
||||||
|
import tags from '@hcengineering/tags'
|
||||||
|
import view from '@hcengineering/view-resources/src/plugin'
|
||||||
import { getNearest } from '../utils'
|
import { getNearest } from '../utils'
|
||||||
import MenuClose from './icons/MenuClose.svelte'
|
|
||||||
import MenuOpen from './icons/MenuOpen.svelte'
|
|
||||||
import CreateToDo from './CreateToDo.svelte'
|
import CreateToDo from './CreateToDo.svelte'
|
||||||
import ToDoGroup from './ToDoGroup.svelte'
|
import ToDoGroup from './ToDoGroup.svelte'
|
||||||
|
import MenuClose from './icons/MenuClose.svelte'
|
||||||
|
import MenuOpen from './icons/MenuOpen.svelte'
|
||||||
import IconDiff from './icons/Diff.svelte'
|
import IconDiff from './icons/Diff.svelte'
|
||||||
import time from '../plugin'
|
import time from '../plugin'
|
||||||
import tags, { TagElement } from '@hcengineering/tags'
|
|
||||||
import tracker, { Project } from '@hcengineering/tracker'
|
|
||||||
import view from '@hcengineering/view-resources/src/plugin'
|
|
||||||
|
|
||||||
export let mode: ToDosMode
|
export let mode: ToDosMode
|
||||||
export let tag: Ref<TagElement> | undefined
|
export let tag: Ref<TagElement> | undefined
|
||||||
@ -29,7 +47,6 @@
|
|||||||
const doneQuery = createQuery()
|
const doneQuery = createQuery()
|
||||||
const inboxQuery = createQuery()
|
const inboxQuery = createQuery()
|
||||||
const activeQuery = createQuery()
|
const activeQuery = createQuery()
|
||||||
|
|
||||||
const tagsQuery = createQuery()
|
const tagsQuery = createQuery()
|
||||||
const projectsQuery = createQuery()
|
const projectsQuery = createQuery()
|
||||||
|
|
||||||
@ -129,7 +146,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
limit: 200,
|
limit: 200,
|
||||||
sort: { modifiedOn: SortingOrder.Ascending },
|
sort: { rank: SortingOrder.Ascending },
|
||||||
lookup: { _id: { workslots: time.class.WorkSlot } }
|
lookup: { _id: { workslots: time.class.WorkSlot } }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -147,7 +164,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
limit: 200,
|
limit: 200,
|
||||||
sort: { modifiedOn: SortingOrder.Ascending }
|
sort: { rank: SortingOrder.Ascending }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -245,8 +262,6 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
todos.sort((a, b) => (a.nearest?.date ?? 0) - (b.nearest?.date ?? 0))
|
|
||||||
scheduled.sort((a, b) => (a.nearest?.date ?? 0) - (b.nearest?.date ?? 0))
|
|
||||||
groups.set(
|
groups.set(
|
||||||
time.string.ToDos,
|
time.string.ToDos,
|
||||||
todos.map((p) => p.todo)
|
todos.map((p) => p.todo)
|
||||||
@ -309,8 +324,6 @@
|
|||||||
{mode}
|
{mode}
|
||||||
{projects}
|
{projects}
|
||||||
{largeSize}
|
{largeSize}
|
||||||
on:dragstart
|
|
||||||
on:dragend
|
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</Scroller>
|
</Scroller>
|
||||||
@ -322,7 +335,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
// height: 100%;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
23
plugins/time-resources/src/dragging.ts
Normal file
23
plugins/time-resources/src/dragging.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
|
import type { ToDo } from '@hcengineering/time'
|
||||||
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
|
interface ToDoDragging {
|
||||||
|
item: ToDo | null
|
||||||
|
itemIndex: number | null
|
||||||
|
groupName: IntlString | null
|
||||||
|
projectId: string | false | null
|
||||||
|
overItemIndex: number | null
|
||||||
|
overGroupName: IntlString | null
|
||||||
|
overProjectId: string | false | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dragging = writable<ToDoDragging>({
|
||||||
|
item: null,
|
||||||
|
itemIndex: null,
|
||||||
|
groupName: null,
|
||||||
|
projectId: null,
|
||||||
|
overItemIndex: null,
|
||||||
|
overGroupName: null,
|
||||||
|
overProjectId: null
|
||||||
|
})
|
@ -37,6 +37,7 @@
|
|||||||
"@hcengineering/calendar": "^0.6.17",
|
"@hcengineering/calendar": "^0.6.17",
|
||||||
"@hcengineering/task": "^0.6.13",
|
"@hcengineering/task": "^0.6.13",
|
||||||
"@hcengineering/platform": "^0.6.9",
|
"@hcengineering/platform": "^0.6.9",
|
||||||
"@hcengineering/ui": "^0.6.11"
|
"@hcengineering/ui": "^0.6.11",
|
||||||
|
"@hcengineering/rank": "^0.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import { IntlString, plugin } from '@hcengineering/platform'
|
|||||||
import { Event, Visibility } from '@hcengineering/calendar'
|
import { Event, Visibility } from '@hcengineering/calendar'
|
||||||
import { AnyComponent } from '@hcengineering/ui'
|
import { AnyComponent } from '@hcengineering/ui'
|
||||||
import { Person } from '@hcengineering/contact'
|
import { Person } from '@hcengineering/contact'
|
||||||
|
import type { Rank } from '@hcengineering/rank'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -49,6 +50,7 @@ export interface ToDo extends AttachedDoc {
|
|||||||
user: Ref<Person>
|
user: Ref<Person>
|
||||||
attachedSpace?: Ref<Space>
|
attachedSpace?: Ref<Space>
|
||||||
labels?: number
|
labels?: number
|
||||||
|
rank: Rank
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +21,7 @@ import core, {
|
|||||||
Doc,
|
Doc,
|
||||||
DocumentUpdate,
|
DocumentUpdate,
|
||||||
Ref,
|
Ref,
|
||||||
|
SortingOrder,
|
||||||
Status,
|
Status,
|
||||||
Tx,
|
Tx,
|
||||||
TxCUD,
|
TxCUD,
|
||||||
@ -38,7 +39,7 @@ import {
|
|||||||
getNotificationContent,
|
getNotificationContent,
|
||||||
isShouldNotifyTx
|
isShouldNotifyTx
|
||||||
} from '@hcengineering/server-notification-resources'
|
} from '@hcengineering/server-notification-resources'
|
||||||
import task from '@hcengineering/task'
|
import task, { makeRank } from '@hcengineering/task'
|
||||||
import tracker, { Issue, IssueStatus, Project, TimeSpendReport } from '@hcengineering/tracker'
|
import tracker, { Issue, IssueStatus, Project, TimeSpendReport } from '@hcengineering/tracker'
|
||||||
import serverTime, { OnToDo, ToDoFactory } from '@hcengineering/server-time'
|
import serverTime, { OnToDo, ToDoFactory } from '@hcengineering/server-time'
|
||||||
import time, { ProjectToDo, ToDo, ToDoPriority, TodoAutomationHelper, WorkSlot } from '@hcengineering/time'
|
import time, { ProjectToDo, ToDo, ToDoPriority, TodoAutomationHelper, WorkSlot } from '@hcengineering/time'
|
||||||
@ -444,6 +445,20 @@ async function getIssueToDoData (
|
|||||||
): Promise<AttachedData<ProjectToDo> | undefined> {
|
): Promise<AttachedData<ProjectToDo> | undefined> {
|
||||||
const acc = await getPersonAccount(user, control)
|
const acc = await getPersonAccount(user, control)
|
||||||
if (acc === undefined) return
|
if (acc === undefined) return
|
||||||
|
const firstTodoItem = (
|
||||||
|
await control.findAll(
|
||||||
|
time.class.ToDo,
|
||||||
|
{
|
||||||
|
user: acc.person,
|
||||||
|
doneOn: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
limit: 1,
|
||||||
|
sort: { rank: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)[0]
|
||||||
|
const rank = makeRank(undefined, firstTodoItem?.rank)
|
||||||
const data: AttachedData<ProjectToDo> = {
|
const data: AttachedData<ProjectToDo> = {
|
||||||
attachedSpace: issue.space,
|
attachedSpace: issue.space,
|
||||||
workslots: 0,
|
workslots: 0,
|
||||||
@ -451,7 +466,8 @@ async function getIssueToDoData (
|
|||||||
priority: ToDoPriority.NoPriority,
|
priority: ToDoPriority.NoPriority,
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
title: issue.title,
|
title: issue.title,
|
||||||
user: acc.person
|
user: acc.person,
|
||||||
|
rank
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user