mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-30 12:15:51 +00:00
UBERF-4928: Indexing fixes (#4357)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
266b82e284
commit
960c7d9963
@ -484,24 +484,6 @@ export function createModel (builder: Builder): void {
|
||||
actions: [view.action.Delete, task.action.Move]
|
||||
})
|
||||
|
||||
// TODO: update query when nested query is available
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: board.actionImpl.ConvertToCard,
|
||||
label: board.string.ConvertToCard,
|
||||
icon: board.icon.Card,
|
||||
category: board.category.Card,
|
||||
query: {
|
||||
attachedToClass: task.class.TodoItem
|
||||
},
|
||||
input: 'any',
|
||||
target: task.class.TodoItem,
|
||||
context: { mode: ['context', 'browser'] }
|
||||
},
|
||||
board.action.ConvertToCard
|
||||
)
|
||||
|
||||
createAction(builder, {
|
||||
...viewTemplates.open,
|
||||
target: board.class.Board,
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Employee, Person } from '@hcengineering/contact'
|
||||
import type { Person } from '@hcengineering/contact'
|
||||
import contact from '@hcengineering/contact'
|
||||
import {
|
||||
ClassifierKind,
|
||||
@ -41,16 +41,15 @@ import {
|
||||
Mixin,
|
||||
Model,
|
||||
Prop,
|
||||
ReadOnly,
|
||||
TypeBoolean,
|
||||
TypeDate,
|
||||
TypeMarkup,
|
||||
TypeRecord,
|
||||
TypeRef,
|
||||
TypeString,
|
||||
UX,
|
||||
type Builder,
|
||||
type MigrationClient,
|
||||
ReadOnly
|
||||
type MigrationClient
|
||||
} from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
@ -65,7 +64,6 @@ import { getEmbeddedLabel, type Asset, type IntlString, type Resource } from '@h
|
||||
import setting from '@hcengineering/setting'
|
||||
import tags from '@hcengineering/tags'
|
||||
import {
|
||||
type TaskStatusFactory,
|
||||
calculateStatuses,
|
||||
findStatusAttr,
|
||||
type KanbanCard,
|
||||
@ -76,11 +74,11 @@ import {
|
||||
type ProjectTypeDescriptor,
|
||||
type Sequence,
|
||||
type Task,
|
||||
type TaskStatusFactory,
|
||||
type TaskType,
|
||||
type TaskTypeClass,
|
||||
type TaskTypeDescriptor,
|
||||
type TaskTypeKind,
|
||||
type TodoItem
|
||||
type TaskTypeKind
|
||||
} from '@hcengineering/task'
|
||||
import type { AnyComponent } from '@hcengineering/ui'
|
||||
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
|
||||
@ -121,7 +119,10 @@ export class TTask extends TAttachedDoc implements Task {
|
||||
@Prop(TypeDate(), task.string.DueDate, { editor: task.component.DueDateEditor })
|
||||
dueDate!: Timestamp | null
|
||||
|
||||
declare rank: string
|
||||
@Prop(TypeString(), task.string.Rank)
|
||||
@Index(IndexKind.IndexedDsc)
|
||||
@Hidden()
|
||||
rank!: string
|
||||
|
||||
@Prop(Collection(tags.class.TagReference, task.string.TaskLabels), task.string.TaskLabels)
|
||||
labels?: number
|
||||
@ -135,28 +136,6 @@ export class TTask extends TAttachedDoc implements Task {
|
||||
isDone?: boolean
|
||||
}
|
||||
|
||||
@Model(task.class.TodoItem, core.class.AttachedDoc, DOMAIN_TASK)
|
||||
@UX(task.string.Todo)
|
||||
export class TTodoItem extends TAttachedDoc implements TodoItem {
|
||||
@Prop(TypeMarkup(), task.string.TodoName, task.icon.Task)
|
||||
@Index(IndexKind.FullText)
|
||||
name!: string
|
||||
|
||||
@Prop(TypeRef(contact.mixin.Employee), task.string.TaskAssignee)
|
||||
assignee!: Ref<Employee> | null
|
||||
|
||||
@Prop(TypeBoolean(), task.string.TaskDone)
|
||||
done!: boolean
|
||||
|
||||
@Prop(TypeDate(), task.string.TaskDueTo)
|
||||
dueTo!: Timestamp | null
|
||||
|
||||
@Prop(Collection(task.class.TodoItem), task.string.Todos)
|
||||
items!: number
|
||||
|
||||
declare rank: string
|
||||
}
|
||||
|
||||
@Mixin(task.mixin.KanbanCard, core.class.Class)
|
||||
export class TKanbanCard extends TClass implements KanbanCard {
|
||||
card!: AnyComponent
|
||||
@ -320,7 +299,6 @@ export function createModel (builder: Builder): void {
|
||||
TKanbanCard,
|
||||
TSequence,
|
||||
TTask,
|
||||
TTodoItem,
|
||||
TProject,
|
||||
TProjectType,
|
||||
TTaskType,
|
||||
@ -418,14 +396,6 @@ export function createModel (builder: Builder): void {
|
||||
task.viewlet.Dashboard
|
||||
)
|
||||
|
||||
builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.CollectionEditor, {
|
||||
editor: task.component.Todos
|
||||
})
|
||||
|
||||
builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: task.component.TodoItemPresenter
|
||||
})
|
||||
|
||||
builder.mixin(task.class.TaskType, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: task.component.TaskTypePresenter
|
||||
})
|
||||
@ -436,46 +406,6 @@ export function createModel (builder: Builder): void {
|
||||
|
||||
classPresenter(builder, task.class.TaskType, task.component.TaskTypePresenter, task.component.TaskTypePresenter)
|
||||
|
||||
createAction(builder, {
|
||||
label: task.string.MarkAsDone,
|
||||
icon: task.icon.TodoCheck,
|
||||
action: view.actionImpl.UpdateDocument,
|
||||
actionProps: {
|
||||
key: 'done',
|
||||
value: true
|
||||
},
|
||||
input: 'focus',
|
||||
category: task.category.Task,
|
||||
query: {
|
||||
done: false
|
||||
},
|
||||
target: task.class.TodoItem,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'edit'
|
||||
}
|
||||
})
|
||||
|
||||
createAction(builder, {
|
||||
label: task.string.MarkAsUndone,
|
||||
icon: task.icon.TodoUnCheck,
|
||||
action: view.actionImpl.UpdateDocument,
|
||||
actionProps: {
|
||||
key: 'done',
|
||||
value: false
|
||||
},
|
||||
input: 'focus',
|
||||
category: task.category.Task,
|
||||
query: {
|
||||
done: true
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'edit'
|
||||
},
|
||||
target: task.class.TodoItem
|
||||
})
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
|
@ -32,8 +32,6 @@ export default mergeIds(taskId, task, {
|
||||
},
|
||||
actionImpl: {
|
||||
EditStatuses: '' as ViewAction,
|
||||
TodoItemMarkDone: '' as ViewAction,
|
||||
TodoItemMarkUnDone: '' as ViewAction,
|
||||
ArchiveSpace: '' as ViewAction,
|
||||
UnarchiveSpace: '' as ViewAction,
|
||||
SelectStatus: '' as ViewAction
|
||||
@ -51,8 +49,6 @@ export default mergeIds(taskId, task, {
|
||||
StatePresenter: '' as AnyComponent,
|
||||
StateEditor: '' as AnyComponent,
|
||||
KanbanView: '' as AnyComponent,
|
||||
Todos: '' as AnyComponent,
|
||||
TodoItemPresenter: '' as AnyComponent,
|
||||
StatusTableView: '' as AnyComponent,
|
||||
TaskHeader: '' as AnyComponent,
|
||||
Dashboard: '' as AnyComponent,
|
||||
|
@ -224,10 +224,6 @@ export class TIssue extends TTask implements Issue {
|
||||
@Prop(TypeDate(DateRangeMode.DATETIME), tracker.string.DueDate)
|
||||
declare dueDate: Timestamp | null
|
||||
|
||||
@Prop(TypeString(), tracker.string.Rank)
|
||||
@Hidden()
|
||||
declare rank: string
|
||||
|
||||
@Prop(TypeRef(tracker.class.Milestone), tracker.string.Milestone, { icon: tracker.icon.Milestone })
|
||||
@Index(IndexKind.Indexed)
|
||||
milestone!: Ref<Milestone> | null
|
||||
|
@ -114,7 +114,9 @@ export enum IndexKind {
|
||||
*
|
||||
* Also mean to include into Elastic search.
|
||||
*/
|
||||
Indexed
|
||||
Indexed,
|
||||
// Same as indexed but for descending
|
||||
IndexedDsc
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -184,7 +184,7 @@ export function isFullTextAttribute (attr: AnyAttribute): boolean {
|
||||
* @public
|
||||
*/
|
||||
export function isIndexedAttribute (attr: AnyAttribute): boolean {
|
||||
return attr.index === IndexKind.Indexed
|
||||
return attr.index === IndexKind.Indexed || attr.index === IndexKind.IndexedDsc
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,27 +19,23 @@
|
||||
import core, { Class, Doc, Mixin, Ref, Space } from '@hcengineering/core'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import type { State, TodoItem } from '@hcengineering/task'
|
||||
import task from '@hcengineering/task'
|
||||
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||
import {
|
||||
Button,
|
||||
CircleButton,
|
||||
EditBox,
|
||||
getEventPopupPositionElement,
|
||||
IconAdd,
|
||||
IconMoreH,
|
||||
Label,
|
||||
getEventPopupPositionElement,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { ContextMenu, DocAttributeBar, invokeAction, ParentsNavigator } from '@hcengineering/view-resources'
|
||||
import { ContextMenu, DocAttributeBar, ParentsNavigator, invokeAction } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import board from '../plugin'
|
||||
import { getCardActions } from '../utils/CardActionUtils'
|
||||
import { updateCard } from '../utils/CardUtils'
|
||||
import CardActions from './editor/CardActions.svelte'
|
||||
import CardChecklist from './editor/CardChecklist.svelte'
|
||||
import AddChecklist from './popups/AddChecklist.svelte'
|
||||
|
||||
export let _id: Ref<Card>
|
||||
export let _class: Ref<Class<Card>>
|
||||
@ -48,13 +44,12 @@
|
||||
const cardQuery = createQuery()
|
||||
const stateQuery = createQuery()
|
||||
const spaceQuery = createQuery()
|
||||
const checklistsQuery = createQuery()
|
||||
|
||||
let object: Card | undefined
|
||||
let state: State | undefined
|
||||
let space: Space | undefined
|
||||
let handleMove: (e: Event) => void
|
||||
let checklists: TodoItem[] = []
|
||||
|
||||
const mixins: Mixin<Doc>[] = []
|
||||
const allowedCollections = ['labels']
|
||||
const ignoreKeys = ['isArchived', 'location', 'title', 'description', 'status', 'number', 'assignee']
|
||||
@ -65,16 +60,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addChecklist (e: Event) {
|
||||
showPopup(AddChecklist, { value: object }, getEventPopupPositionElement(e))
|
||||
}
|
||||
|
||||
$: cardQuery.query(_class, { _id }, (result) => {
|
||||
object = result[0]
|
||||
})
|
||||
|
||||
$: object?.status &&
|
||||
stateQuery.query(task.class.State, { _id: object.status }, (result) => {
|
||||
stateQuery.query(core.class.Status, { _id: object.status }, (result) => {
|
||||
state = result[0]
|
||||
})
|
||||
|
||||
@ -83,11 +74,6 @@
|
||||
space = result[0]
|
||||
})
|
||||
|
||||
$: object &&
|
||||
checklistsQuery.query(task.class.TodoItem, { space: object.space, attachedTo: object._id }, (result) => {
|
||||
checklists = result
|
||||
})
|
||||
|
||||
getCardActions(client, { _id: board.action.Move }).then(async (result) => {
|
||||
if (result[0]) {
|
||||
handleMove = (e) => {
|
||||
@ -157,15 +143,6 @@
|
||||
<div class="mt-6">
|
||||
<Attachments objectId={_id} {_class} space={object.space} attachments={object.attachments ?? 0} />
|
||||
</div>
|
||||
<div class="flex-row-center mt-6">
|
||||
<span class="text-xl font-medium caption-color mr-3"><Label label={board.string.Checklists} /></span>
|
||||
<CircleButton icon={IconAdd} size="small" selected on:click={addChecklist} />
|
||||
</div>
|
||||
<div class="mr-2 ml-2 mb-4">
|
||||
{#each checklists as checklist}
|
||||
<CardChecklist value={checklist} />
|
||||
{/each}
|
||||
</div>
|
||||
<svelte:fragment slot="custom-attributes" let:direction>
|
||||
{#if direction === 'column'}
|
||||
<DocAttributeBar {object} {mixins} {ignoreKeys} {allowedCollections} />
|
||||
|
@ -19,27 +19,26 @@
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import type { Ref, WithLookup } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import view from '@hcengineering/view'
|
||||
import tags from '@hcengineering/tags'
|
||||
import { ChatMessagesPresenter } from '@hcengineering/notification-resources'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import tags from '@hcengineering/tags'
|
||||
import {
|
||||
Button,
|
||||
Component,
|
||||
EditBox,
|
||||
getPopupPositionElement,
|
||||
Icon,
|
||||
IconMoreV,
|
||||
Label,
|
||||
getPopupPositionElement,
|
||||
numberToHexColor,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { ContextMenu } from '@hcengineering/view-resources'
|
||||
import board from '../plugin'
|
||||
import DatePresenter from './presenters/DatePresenter.svelte'
|
||||
import { hasDate, openCardPanel, updateCard, updateCardMembers } from '../utils/CardUtils'
|
||||
import CheckListsPresenter from './presenters/ChecklistsPresenter.svelte'
|
||||
import DatePresenter from './presenters/DatePresenter.svelte'
|
||||
import NotificationPresenter from './presenters/NotificationPresenter.svelte'
|
||||
import { ChatMessagesPresenter } from '@hcengineering/notification-resources'
|
||||
|
||||
export let object: WithLookup<Card>
|
||||
|
||||
@ -198,11 +197,6 @@
|
||||
<ChatMessagesPresenter value={object.comments} {object} size="small" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if (object.todoItems ?? 0) > 0}
|
||||
<div class="float-left">
|
||||
<CheckListsPresenter value={object} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if (object.labels ?? 0) > 0}
|
||||
<div class="float-left">
|
||||
<Component
|
||||
|
@ -1,315 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2022 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 { Employee } from '@hcengineering/contact'
|
||||
import { EmployeeBox } from '@hcengineering/contact-resources'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import type { TodoItem } from '@hcengineering/task'
|
||||
import task, { calcRank } from '@hcengineering/task'
|
||||
import {
|
||||
Button,
|
||||
CheckBox,
|
||||
DateRangePresenter,
|
||||
IconAdd,
|
||||
IconDelete,
|
||||
IconMoreH,
|
||||
Progress,
|
||||
TextAreaEditor,
|
||||
getEventPopupPositionElement,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { ContextMenu, HTMLPresenter } from '@hcengineering/view-resources'
|
||||
|
||||
import board from '../../plugin'
|
||||
import { getDateIcon } from '../../utils/BoardUtils'
|
||||
|
||||
export let value: TodoItem
|
||||
const client = getClient()
|
||||
const checklistItemsQuery = createQuery()
|
||||
let checklistItems: TodoItem[] = []
|
||||
let done = 0
|
||||
let isEditingName: boolean = false
|
||||
let isAddingItem: boolean = false
|
||||
let hideDoneItems: boolean = false
|
||||
let newItemName = ''
|
||||
let editingItemId: Ref<TodoItem> | undefined = undefined
|
||||
let hovered: Ref<TodoItem> | undefined = undefined
|
||||
let dragItem: TodoItem | undefined = undefined
|
||||
let dragOverItem: TodoItem | undefined = undefined
|
||||
|
||||
function deleteChecklist () {
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
showPopup(
|
||||
MessageBox,
|
||||
{
|
||||
label: board.string.DeleteChecklist,
|
||||
message: board.string.DeleteChecklistConfirm
|
||||
},
|
||||
undefined,
|
||||
(result?: boolean) => {
|
||||
if (result === true) {
|
||||
client.removeCollection(
|
||||
value._class,
|
||||
value.space,
|
||||
value._id,
|
||||
value.attachedTo,
|
||||
value.attachedToClass,
|
||||
value.collection
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function startAddingItem () {
|
||||
isAddingItem = true
|
||||
}
|
||||
|
||||
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,
|
||||
rank: calcRank(prev, undefined)
|
||||
}
|
||||
if (item.name.length <= 0) {
|
||||
return
|
||||
}
|
||||
await client.addCollection(task.class.TodoItem, value.space, value._id, value._class, 'items', item)
|
||||
}
|
||||
|
||||
function updateItemName (item: TodoItem, name: string) {
|
||||
if (name === undefined || name.length === 0 || name === item.name) return
|
||||
client.update(item, { name })
|
||||
}
|
||||
|
||||
function updateName (event: CustomEvent<string>) {
|
||||
isEditingName = false
|
||||
updateItemName(value, event.detail)
|
||||
}
|
||||
|
||||
function updateItemAssignee (item: TodoItem, assignee: Ref<Employee>) {
|
||||
client.update(item, { assignee })
|
||||
}
|
||||
|
||||
function updateDueDate (item: TodoItem, dueTo: number) {
|
||||
client.update(item, { dueTo })
|
||||
}
|
||||
|
||||
async function setDoneToChecklistItem (item: TodoItem, event: CustomEvent<boolean>) {
|
||||
const isDone = event.detail
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
await client.update(item, { done: isDone })
|
||||
}
|
||||
|
||||
function showItemMenu (item: TodoItem, e?: Event) {
|
||||
showPopup(ContextMenu, { object: item }, getEventPopupPositionElement(e))
|
||||
}
|
||||
|
||||
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"
|
||||
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">
|
||||
<TextAreaEditor
|
||||
value={value.name}
|
||||
on:submit={updateName}
|
||||
on:cancel={() => {
|
||||
isEditingName = false
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="flex-grow fs-title"
|
||||
on:click={() => {
|
||||
isEditingName = true
|
||||
}}
|
||||
>
|
||||
{value.name}
|
||||
</div>
|
||||
{#if done > 0}
|
||||
<Button
|
||||
label={hideDoneItems ? board.string.ShowDoneChecklistItems : board.string.HideDoneChecklistItems}
|
||||
labelParams={{ done }}
|
||||
kind="ghost"
|
||||
size="small"
|
||||
on:click={() => {
|
||||
hideDoneItems = !hideDoneItems
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<Button icon={IconAdd} kind="ghost" size="small" on:click={startAddingItem} />
|
||||
<Button icon={IconDelete} kind="ghost" size="small" on:click={deleteChecklist} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-row-stretch mb-2 mt-1">
|
||||
<div class="w-9 text-sm pl-1 pr-1">
|
||||
{checklistItems.length > 0 ? Math.round((done / checklistItems.length) * 100) : 0}%
|
||||
</div>
|
||||
<div class="flex-center flex-grow w-full">
|
||||
<Progress min={0} max={checklistItems?.length ?? 0} value={done} />
|
||||
</div>
|
||||
</div>
|
||||
{#each checklistItems.filter((item) => !hideDoneItems || !item.done) as item}
|
||||
<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
|
||||
}}
|
||||
on:focus={() => {
|
||||
hovered = item._id
|
||||
}}
|
||||
on:mouseout={() => {
|
||||
hovered = undefined
|
||||
}}
|
||||
on:blur={() => {
|
||||
hovered = undefined
|
||||
}}
|
||||
>
|
||||
<div class="w-9 flex items-center">
|
||||
<CheckBox bind:checked={item.done} on:value={(event) => setDoneToChecklistItem(item, event)} />
|
||||
</div>
|
||||
{#if editingItemId === item._id}
|
||||
<div class="flex-grow">
|
||||
<TextAreaEditor
|
||||
value={item.name}
|
||||
on:submit={(event) => {
|
||||
editingItemId = undefined
|
||||
updateItemName(item, event.detail)
|
||||
}}
|
||||
on:cancel={() => {
|
||||
editingItemId = undefined
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="flex-col justify-center flex-gap-1 w-full"
|
||||
class:text-line-through={item.done}
|
||||
on:click={() => {
|
||||
editingItemId = item._id
|
||||
}}
|
||||
>
|
||||
<HTMLPresenter bind:value={item.name} />
|
||||
</div>
|
||||
<div class="flex-center gap-1">
|
||||
<DateRangePresenter
|
||||
editable
|
||||
bind:value={item.dueTo}
|
||||
icon={getDateIcon(item)}
|
||||
on:change={(e) => {
|
||||
updateDueDate(item, e.detail)
|
||||
}}
|
||||
noShift
|
||||
/>
|
||||
<EmployeeBox
|
||||
label={board.string.Assignee}
|
||||
bind:value={item.assignee}
|
||||
allowDeselect={true}
|
||||
on:change={(e) => {
|
||||
updateItemAssignee(item, e.detail)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconMoreH}
|
||||
kind="ghost"
|
||||
size="small"
|
||||
on:click={(e) => {
|
||||
showItemMenu(item, e)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<div class="flex-row-stretch mt-2 mb-2">
|
||||
<div class="w-9" />
|
||||
{#if isAddingItem}
|
||||
<div class="w-full p-1">
|
||||
<TextAreaEditor
|
||||
bind:value={newItemName}
|
||||
on:submit={addItem}
|
||||
on:cancel={() => {
|
||||
newItemName = ''
|
||||
isAddingItem = false
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
@ -1,137 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import { Card } from '@hcengineering/board'
|
||||
import { WithLookup } from '@hcengineering/core'
|
||||
import task, { calcRank, TodoItem } from '@hcengineering/task'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import presentation, { Card as Popup, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Label, Dropdown, EditBox, themeStore } from '@hcengineering/ui'
|
||||
import type { ListItem } from '@hcengineering/ui'
|
||||
|
||||
import board from '../../plugin'
|
||||
|
||||
export let value: Card
|
||||
|
||||
const noneListItem: ListItem = {
|
||||
_id: 'none',
|
||||
label: ''
|
||||
}
|
||||
|
||||
let lastCardList: TodoItem | undefined = undefined
|
||||
let name: string | undefined
|
||||
let selectedTemplate: ListItem | undefined = undefined
|
||||
let templateListItems: ListItem[] = [noneListItem]
|
||||
let templatesMap = new Map<string, TodoItem>()
|
||||
const client = getClient()
|
||||
const templatesQuery = createQuery()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
translate(board.string.ChecklistDropdownNone, {}, $themeStore.language).then((result) => {
|
||||
noneListItem.label = result
|
||||
})
|
||||
|
||||
async function addChecklist () {
|
||||
if (!name || name.trim().length <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const template = selectedTemplate ? templatesMap.get(selectedTemplate._id) : undefined
|
||||
const items: TodoItem[] = template ? await client.findAll(task.class.TodoItem, { attachedTo: template._id }) : []
|
||||
const checklistRef = await client.addCollection(
|
||||
task.class.TodoItem,
|
||||
value.space,
|
||||
value._id,
|
||||
value._class,
|
||||
'todoItems',
|
||||
{
|
||||
name,
|
||||
done: false,
|
||||
dueTo: null,
|
||||
assignee: null,
|
||||
rank: calcRank(lastCardList)
|
||||
}
|
||||
)
|
||||
if (items.length > 0) {
|
||||
await Promise.all(
|
||||
items.map((item) =>
|
||||
client.addCollection(task.class.TodoItem, value.space, checklistRef, task.class.TodoItem, 'items', {
|
||||
name: item.name,
|
||||
dueTo: item.dueTo,
|
||||
done: item.done,
|
||||
assignee: item.assignee,
|
||||
rank: item.rank
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
$: templatesQuery.query(
|
||||
board.class.Card,
|
||||
{ todoItems: { $gt: 0 } },
|
||||
(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,
|
||||
fontWeight: 'semi-bold',
|
||||
isSelectable: false
|
||||
})
|
||||
|
||||
for (const todoItem of todoItems) {
|
||||
templateListItems.push({
|
||||
_id: todoItem._id,
|
||||
label: todoItem.name,
|
||||
paddingLeft: 4
|
||||
})
|
||||
templatesMap.set(todoItem._id, todoItem)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ lookup: { _id: { todoItems: task.class.TodoItem } } }
|
||||
)
|
||||
</script>
|
||||
|
||||
<Popup
|
||||
label={board.string.Checklists}
|
||||
okAction={addChecklist}
|
||||
okLabel={presentation.string.Add}
|
||||
canSave={(name?.length ?? 0) > 0}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<div class="flex-col flex-gap-1">
|
||||
<Label label={board.string.Title} />
|
||||
<div class="p-2 mt-1 mb-1 border-divider-color border-radius-1">
|
||||
<EditBox bind:value={name} autoFocus />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-col flex-gap-1">
|
||||
<Label label={board.string.CopyChecklistFrom} />
|
||||
<div class="mt-1 mb-1 w-full">
|
||||
<Dropdown
|
||||
bind:selected={selectedTemplate}
|
||||
items={templateListItems}
|
||||
justify="left"
|
||||
width="100%"
|
||||
placeholder={board.string.ChecklistDropdownNone}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
@ -1,42 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { DatePresenter, Icon } from '@hcengineering/ui'
|
||||
import board, { Card } from '@hcengineering/board'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import task, { TodoItem } from '@hcengineering/task'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { getDateIcon } from '../../utils/BoardUtils'
|
||||
|
||||
export let value: Card
|
||||
export let size: 'small' | 'medium' | 'large' = 'small'
|
||||
|
||||
const todoListQuery = createQuery()
|
||||
let todoLists: Ref<TodoItem>[] = []
|
||||
$: todoListQuery.query(task.class.TodoItem, { space: value.space, attachedTo: value._id }, (result) => {
|
||||
todoLists = result.map(({ _id }) => _id)
|
||||
})
|
||||
const query = createQuery()
|
||||
let done: number, total: number, item: TodoItem
|
||||
$: query.query(
|
||||
task.class.TodoItem,
|
||||
{ space: value.space, attachedTo: { $in: todoLists } },
|
||||
(result) => {
|
||||
total = result.total
|
||||
done = result.filter((t) => t.done).length
|
||||
if (!total) return
|
||||
item = result.reduce((min, cur) =>
|
||||
cur.dueTo === null ? min : min.dueTo === null || cur.dueTo < min.dueTo ? cur : min
|
||||
)
|
||||
},
|
||||
{ total: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if value && (total ?? 0) > 0}
|
||||
<div class="sm-tool-icon ml-1 mr-1">
|
||||
<Icon icon={board.icon.Card} {size} />
|
||||
{done}/{total}
|
||||
{#if item.dueTo !== null}
|
||||
<DatePresenter value={item.dueTo} size="x-small" iconModifier={getDateIcon(item)} kind="ghost" />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
@ -14,10 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
import { type Resources } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import task, { type Project, type TodoItem } from '@hcengineering/task'
|
||||
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import Archive from './components/Archive.svelte'
|
||||
import BoardHeader from './components/BoardHeader.svelte'
|
||||
import BoardMenu from './components/BoardMenu.svelte'
|
||||
@ -39,25 +36,6 @@ import CopyCard from './components/popups/CopyCard.svelte'
|
||||
import DateRangePicker from './components/popups/DateRangePicker.svelte'
|
||||
import MoveCard from './components/popups/MoveCard.svelte'
|
||||
import CardCoverPresenter from './components/presenters/CardCoverPresenter.svelte'
|
||||
import { createCard, getCardFromTodoItem } from './utils/CardUtils'
|
||||
|
||||
async function ConvertToCard (object: TodoItem): Promise<void> {
|
||||
const client = getClient()
|
||||
const todoItemCard = await getCardFromTodoItem(client, object)
|
||||
if (todoItemCard === undefined) return
|
||||
|
||||
// TODO: Add filtering if requierd, or pass a type from UI
|
||||
const project = await client.findOne(task.class.Project, { _id: object.space as Ref<Project> })
|
||||
const taskTypes = await client.findAll(task.class.TaskType, { parent: project?.type })
|
||||
await createCard(client, todoItemCard.space, todoItemCard.status, {
|
||||
title: object.name,
|
||||
assignee: object.assignee,
|
||||
dueDate: object.dueTo,
|
||||
kind: taskTypes[0]._id
|
||||
})
|
||||
|
||||
await client.remove(object)
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
@ -83,8 +61,5 @@ export default async (): Promise<Resources> => ({
|
||||
CoverActionPopup: CardCoverPicker,
|
||||
MoveActionPopup: MoveCard,
|
||||
CopyActionPopup: CopyCard
|
||||
},
|
||||
actionImpl: {
|
||||
ConvertToCard
|
||||
}
|
||||
})
|
||||
|
@ -56,11 +56,6 @@ export default mergeIds(boardId, board, {
|
||||
SelectColor: '' as IntlString,
|
||||
NoColor: '' as IntlString,
|
||||
NoColorInfo: '' as IntlString,
|
||||
Checklists: '' as IntlString,
|
||||
ChecklistDropdownNone: '' as IntlString,
|
||||
ShowDoneChecklistItems: '' as IntlString,
|
||||
HideDoneChecklistItems: '' as IntlString,
|
||||
CopyChecklistFrom: '' as IntlString,
|
||||
Dates: '' as IntlString,
|
||||
Attachments: '' as IntlString,
|
||||
AddAttachment: '' as IntlString,
|
||||
@ -119,9 +114,7 @@ export default mergeIds(boardId, board, {
|
||||
SwitchToCards: '' as IntlString,
|
||||
SearchArchive: '' as IntlString,
|
||||
Size: '' as IntlString,
|
||||
RemoveCover: '' as IntlString,
|
||||
DeleteChecklist: '' as IntlString,
|
||||
DeleteChecklistConfirm: '' as IntlString
|
||||
RemoveCover: '' as IntlString
|
||||
},
|
||||
statusCategory: {
|
||||
Completed: '' as Ref<StatusCategory>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import board, { type Board, type CommonBoardPreference } from '@hcengineering/board'
|
||||
import core, { type Ref, type TxOperations, getCurrentAccount } from '@hcengineering/core'
|
||||
import core, { getCurrentAccount, type Ref, type TxOperations } from '@hcengineering/core'
|
||||
import preference from '@hcengineering/preference'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import type { ProjectType, TodoItem } from '@hcengineering/task'
|
||||
import type { ProjectType } from '@hcengineering/task'
|
||||
import {
|
||||
EastSideColor,
|
||||
FeijoaColor,
|
||||
@ -13,8 +13,7 @@ import {
|
||||
MoodyBlueColor,
|
||||
SalmonColor,
|
||||
SeaBuckthornColor,
|
||||
SeagullColor,
|
||||
areDatesEqual
|
||||
SeagullColor
|
||||
} from '@hcengineering/ui'
|
||||
import { readable } from 'svelte/store'
|
||||
|
||||
@ -50,14 +49,6 @@ export function getBoardAvailableColors (): string[] {
|
||||
SeagullColor
|
||||
]
|
||||
}
|
||||
|
||||
export function getDateIcon (item: TodoItem): 'normal' | 'warning' | 'overdue' {
|
||||
if (item.dueTo === null) return 'normal'
|
||||
const date = new Date()
|
||||
const dueDate = new Date(item.dueTo)
|
||||
return areDatesEqual(date, dueDate) ? 'warning' : dueDate < date ? 'overdue' : 'normal'
|
||||
}
|
||||
|
||||
export const commonBoardPreference = readable<CommonBoardPreference>(undefined, (set) => {
|
||||
createQuery().query(board.class.CommonBoardPreference, { attachedTo: board.app.Board }, (result) => {
|
||||
if (result.length > 0) {
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
type Status
|
||||
} from '@hcengineering/core'
|
||||
import { showPanel } from '@hcengineering/ui'
|
||||
import task, { calcRank, type TodoItem } from '@hcengineering/task'
|
||||
import task, { calcRank } from '@hcengineering/task'
|
||||
import board from '../plugin'
|
||||
|
||||
export async function createCard (
|
||||
@ -43,17 +43,6 @@ export async function createCard (
|
||||
return await client.addCollection(board.class.Card, space, space, board.class.Board, 'cards', value)
|
||||
}
|
||||
|
||||
export async function getCardFromTodoItem (client: Client, todoItem: TodoItem | undefined): Promise<Card | undefined> {
|
||||
if (todoItem === undefined) return
|
||||
if (todoItem.attachedToClass === todoItem._class) {
|
||||
return await getCardFromTodoItem(
|
||||
client,
|
||||
await client.findOne(todoItem._class, { _id: todoItem.attachedTo as Ref<TodoItem> })
|
||||
)
|
||||
}
|
||||
return await client.findOne(board.class.Card, { _id: todoItem.attachedTo as Ref<Card> })
|
||||
}
|
||||
|
||||
export function updateCard (client: Client, card: Card, field: string, value: any): Promise<TxResult> | undefined {
|
||||
if (card === undefined) {
|
||||
return
|
||||
|
@ -41,7 +41,6 @@
|
||||
"TaskUnAssign": "Unassign",
|
||||
"NoTaskForObject": "No tasks defined",
|
||||
"Delete": "Delete",
|
||||
"NoTodoItems": "No to do's defined",
|
||||
"TodoName": "Name",
|
||||
"TodoState": "State",
|
||||
"DoneState": "Done State",
|
||||
@ -70,7 +69,6 @@
|
||||
"CantStatusDeleteError": "There are objects in the given state. Move or delete them first.",
|
||||
"Tasks": "Tasks",
|
||||
"AssignedToMe": "Assigned to me",
|
||||
"TodoItems": "Todos",
|
||||
"Dashboard": "Dashboard",
|
||||
"AllTime": "All time",
|
||||
"RelatedIssues": "Related processes",
|
||||
|
@ -41,7 +41,6 @@
|
||||
"TaskUnAssign": "Сбросить назначение",
|
||||
"NoTaskForObject": "Задачи не определены",
|
||||
"Delete": "Удалить",
|
||||
"NoTodoItems": "Нет to do",
|
||||
"TodoName": "Название",
|
||||
"TodoState": "Статус",
|
||||
"DoneState": "Статус Завершен",
|
||||
@ -70,7 +69,6 @@
|
||||
"CantStatusDeleteError": "Есть объекты с данным статусом. Сначала переместите или удалите их. ",
|
||||
"Tasks": "Задачи",
|
||||
"AssignedToMe": "Назначено на меня",
|
||||
"TodoItems": "Todos",
|
||||
"Dashboard": "Дашборд",
|
||||
"AllTime": "Все время",
|
||||
"RelatedIssues": "Связанные процессы",
|
||||
|
@ -1,88 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { Class, Ref, Space } from '@hcengineering/core'
|
||||
import { Card, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import type { Task, TodoItem } from '@hcengineering/task'
|
||||
import task, { calcRank } from '@hcengineering/task'
|
||||
import { DatePicker, EditBox, Grid } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
|
||||
export let objectId: Ref<Task>
|
||||
export let _class: Ref<Class<Task>>
|
||||
export let space: Ref<Space>
|
||||
|
||||
let name: string
|
||||
const done = false
|
||||
let dueTo: number | null = null
|
||||
let latestItem: TodoItem | undefined = undefined
|
||||
|
||||
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, 'todoItems', {
|
||||
name,
|
||||
assignee: null,
|
||||
done,
|
||||
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
|
||||
label={plugin.string.TodoCreate}
|
||||
okAction={createTodo}
|
||||
canSave={name?.length > 0}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
okLabel={plugin.string.TodoSave}
|
||||
on:changeContent
|
||||
>
|
||||
<Grid column={1} rowGap={1.75}>
|
||||
<EditBox
|
||||
label={plugin.string.TodoDescription}
|
||||
bind:value={name}
|
||||
icon={task.icon.Task}
|
||||
placeholder={plugin.string.TodoDescriptionPlaceholder}
|
||||
autoFocus
|
||||
/>
|
||||
<DatePicker title={plugin.string.TodoDueDate} bind:value={dueTo} />
|
||||
</Grid>
|
||||
</Card>
|
@ -1,85 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { DocumentUpdate, Ref, Timestamp } from '@hcengineering/core'
|
||||
import { Card, getClient } from '@hcengineering/presentation'
|
||||
import type { TodoItem } from '@hcengineering/task'
|
||||
import task from '@hcengineering/task'
|
||||
import { DatePicker, EditBox, Grid } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
|
||||
export let item: TodoItem
|
||||
|
||||
let name: string = ''
|
||||
let dueTo: number | null | undefined = null
|
||||
|
||||
let _itemId: Ref<TodoItem>
|
||||
|
||||
$: if (_itemId !== item._id) {
|
||||
_itemId = item._id
|
||||
name = item.name
|
||||
if (item.dueTo != null) {
|
||||
dueTo = item.dueTo
|
||||
} else {
|
||||
dueTo = null
|
||||
}
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
export function canClose (): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
async function editTodo () {
|
||||
const ops: DocumentUpdate<TodoItem> = {}
|
||||
if (item.name !== name) {
|
||||
ops.name = name
|
||||
}
|
||||
if (item.dueTo !== dueTo) {
|
||||
ops.dueTo = (dueTo ?? null) as unknown as Timestamp
|
||||
}
|
||||
|
||||
if (Object.keys(ops).length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
await client.update(item, ops)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={plugin.string.TodoEdit}
|
||||
okAction={editTodo}
|
||||
canSave={name.length > 0}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
okLabel={plugin.string.TodoSave}
|
||||
on:changeContent
|
||||
>
|
||||
<Grid column={1} rowGap={1.75}>
|
||||
<EditBox
|
||||
label={plugin.string.TodoDescription}
|
||||
bind:value={name}
|
||||
icon={task.icon.Task}
|
||||
placeholder={plugin.string.TodoDescriptionPlaceholder}
|
||||
autoFocus
|
||||
/>
|
||||
<DatePicker title={plugin.string.TodoDueDate} bind:value={dueTo} />
|
||||
</Grid>
|
||||
</Card>
|
@ -1,46 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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 { TodoItem } from '@hcengineering/task'
|
||||
import { closeTooltip, Icon, showPopup } from '@hcengineering/ui'
|
||||
import task from '../../plugin'
|
||||
import EditTodo from './EditTodo.svelte'
|
||||
|
||||
export let value: TodoItem
|
||||
export let inline: boolean = false
|
||||
|
||||
function show (elm: EventTarget | null) {
|
||||
closeTooltip()
|
||||
showPopup(EditTodo, { item: value }, elm as HTMLElement)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="flex-presenter"
|
||||
class:inline-presenter={inline}
|
||||
on:click={(ev) => {
|
||||
show(ev.target)
|
||||
}}
|
||||
>
|
||||
<div class="icon">
|
||||
<Icon icon={task.icon.Task} size={'small'} />
|
||||
</div>
|
||||
<span class="label">{value.name}</span>
|
||||
</div>
|
||||
{/if}
|
@ -1,40 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { WithLookup } from '@hcengineering/core'
|
||||
import type { Task, TodoItem } from '@hcengineering/task'
|
||||
import task from '@hcengineering/task'
|
||||
import { Table } from '@hcengineering/view-resources'
|
||||
import plugin from '../../plugin'
|
||||
|
||||
export let value: WithLookup<Task>
|
||||
|
||||
$: todos = (value.$lookup?.todoItems as TodoItem[]) ?? []
|
||||
</script>
|
||||
|
||||
<div class="flex-col">
|
||||
{#if todos.length > 0}
|
||||
<Table
|
||||
_class={task.class.TodoItem}
|
||||
config={[
|
||||
{ key: 'name', label: plugin.string.TodoName },
|
||||
'dueTo',
|
||||
{ key: 'done', presenter: plugin.component.TodoStatePresenter, label: plugin.string.TodoState }
|
||||
]}
|
||||
options={{}}
|
||||
query={{ attachedTo: value._id }}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
@ -1,44 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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 { getPlatformColor, Label, themeStore } from '@hcengineering/ui'
|
||||
import task from '../../plugin'
|
||||
export let value: boolean
|
||||
|
||||
$: color = value ? getPlatformColor(2, $themeStore.dark) : getPlatformColor(16, $themeStore.dark)
|
||||
$: text = value ? task.string.DoneState : task.string.UndoneState
|
||||
</script>
|
||||
|
||||
{#if value !== undefined}
|
||||
<div class="overflow-label state-container" style="background-color: {color};">
|
||||
<Label label={text} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.state-container {
|
||||
padding: 0.25rem 0.5rem;
|
||||
width: 6.25rem;
|
||||
max-width: 6.25rem;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
letter-spacing: 0.5px;
|
||||
font-size: 0.625rem;
|
||||
color: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
</style>
|
@ -1,93 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { Ref, Space, Doc, Class } from '@hcengineering/core'
|
||||
import type { TodoItem } from '@hcengineering/task'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Button, IconAdd, showPopup, Label, resizeObserver, Scroller } from '@hcengineering/ui'
|
||||
import CreateTodo from './CreateTodo.svelte'
|
||||
import { Table } from '@hcengineering/view-resources'
|
||||
|
||||
import task from '@hcengineering/task'
|
||||
import plugin from '../../plugin'
|
||||
|
||||
export let objectId: Ref<Doc>
|
||||
export let space: Ref<Space>
|
||||
export let _class: Ref<Class<Doc>>
|
||||
|
||||
let todos: TodoItem[] = []
|
||||
|
||||
const query = createQuery()
|
||||
$: query.query(task.class.TodoItem, { attachedTo: objectId }, (result) => {
|
||||
todos = result
|
||||
})
|
||||
|
||||
const createApp = (ev: MouseEvent): void => {
|
||||
showPopup(CreateTodo, { objectId, _class, space }, ev.target as HTMLElement)
|
||||
}
|
||||
let wSection: number
|
||||
</script>
|
||||
|
||||
<div class="antiSection" use:resizeObserver={(element) => (wSection = element.clientWidth)}>
|
||||
<div class="antiSection-header">
|
||||
<span class="antiSection-header__title">
|
||||
<Label label={plugin.string.Todos} />
|
||||
</span>
|
||||
<Button icon={IconAdd} kind={'ghost'} on:click={createApp} />
|
||||
</div>
|
||||
{#if todos.length > 0}
|
||||
{#if wSection < 640}
|
||||
<Scroller horizontal>
|
||||
<Table
|
||||
_class={task.class.TodoItem}
|
||||
config={[
|
||||
{ key: '', label: plugin.string.TodoName },
|
||||
'dueTo',
|
||||
{ key: 'done', presenter: plugin.component.TodoStatePresenter, label: plugin.string.TodoState }
|
||||
]}
|
||||
options={{
|
||||
sort: {
|
||||
rank: 1
|
||||
}
|
||||
}}
|
||||
query={{ attachedTo: objectId }}
|
||||
/>
|
||||
</Scroller>
|
||||
{:else}
|
||||
<Table
|
||||
_class={task.class.TodoItem}
|
||||
config={[
|
||||
{ key: '', label: plugin.string.TodoName },
|
||||
'dueTo',
|
||||
{ key: 'done', presenter: plugin.component.TodoStatePresenter, label: plugin.string.TodoState }
|
||||
]}
|
||||
options={{
|
||||
sort: {
|
||||
rank: 1
|
||||
}
|
||||
}}
|
||||
query={{ attachedTo: objectId }}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="antiSection-empty solid flex-col-center mt-3">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span class="text-sm over-underline" on:click={createApp}>
|
||||
<Label label={plugin.string.NoTodoItems} />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
@ -63,10 +63,6 @@ import TaskTypeClassPresenter from './components/taskTypes/TaskTypeClassPresente
|
||||
import ProjectTypeClassPresenter from './components/taskTypes/ProjectTypeClassPresenter.svelte'
|
||||
import TaskTypePresenter from './components/taskTypes/TaskTypePresenter.svelte'
|
||||
import ProjectTypePresenter from './components/projectTypes/ProjectTypePresenter.svelte'
|
||||
import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
|
||||
import TodoItemsPopup from './components/todos/TodoItemsPopup.svelte'
|
||||
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
|
||||
import Todos from './components/todos/Todos.svelte'
|
||||
import StateIconPresenter from './components/state/StateIconPresenter.svelte'
|
||||
import TaskKindSelector from './components/taskTypes/TaskKindSelector.svelte'
|
||||
|
||||
@ -124,16 +120,12 @@ export default async (): Promise<Resources> => ({
|
||||
KanbanView,
|
||||
StatePresenter,
|
||||
StateEditor,
|
||||
Todos,
|
||||
TodoItemPresenter,
|
||||
TodoStatePresenter,
|
||||
StatusTableView,
|
||||
TaskHeader,
|
||||
ProjectEditor,
|
||||
ProjectTypeSelector,
|
||||
AssignedTasks,
|
||||
StateRefPresenter,
|
||||
TodoItemsPopup,
|
||||
DueDateEditor,
|
||||
CreateStatePopup,
|
||||
StatusSelector,
|
||||
|
@ -44,7 +44,6 @@ export default mergeIds(taskId, task, {
|
||||
UploadDropFilesHere: '' as IntlString,
|
||||
NoTaskForObject: '' as IntlString,
|
||||
Delete: '' as IntlString,
|
||||
NoTodoItems: '' as IntlString,
|
||||
TodoState: '' as IntlString,
|
||||
DoneState: '' as IntlString,
|
||||
UndoneState: '' as IntlString,
|
||||
|
@ -19,7 +19,6 @@ import {
|
||||
Attribute,
|
||||
Class,
|
||||
Doc,
|
||||
Markup,
|
||||
Mixin,
|
||||
Ref,
|
||||
Space,
|
||||
@ -27,11 +26,11 @@ import {
|
||||
StatusCategory,
|
||||
Timestamp
|
||||
} from '@hcengineering/core'
|
||||
import { NotificationType } from '@hcengineering/notification'
|
||||
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import type { AnyComponent, ComponentExtensionId } from '@hcengineering/ui'
|
||||
import { Action, IconProps, ViewletDescriptor } from '@hcengineering/view'
|
||||
import { NotificationType } from '@hcengineering/notification'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -56,21 +55,9 @@ export interface Task extends AttachedDoc, DocWithRank {
|
||||
dueDate: Timestamp | null
|
||||
comments?: number
|
||||
attachments?: number
|
||||
todoItems?: number
|
||||
labels?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface TodoItem extends AttachedDoc, DocWithRank {
|
||||
name: Markup
|
||||
assignee: Ref<Person> | null
|
||||
done: boolean
|
||||
dueTo: Timestamp | null
|
||||
items?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -252,7 +239,6 @@ const task = plugin(taskId, {
|
||||
MarkAsUndone: '' as IntlString,
|
||||
Kanban: '' as IntlString,
|
||||
ApplicationLabelTask: '' as IntlString,
|
||||
TodoItems: '' as IntlString,
|
||||
AssignedToMe: '' as IntlString,
|
||||
Dashboard: '' as IntlString,
|
||||
ProjectTypes: '' as IntlString,
|
||||
@ -261,7 +247,6 @@ const task = plugin(taskId, {
|
||||
},
|
||||
class: {
|
||||
Sequence: '' as Ref<Class<Sequence>>,
|
||||
TodoItem: '' as Ref<Class<TodoItem>>,
|
||||
ProjectTypeDescriptor: '' as Ref<Class<ProjectTypeDescriptor>>,
|
||||
ProjectType: '' as Ref<Class<ProjectType>>,
|
||||
Project: '' as Ref<Class<Project>>,
|
||||
@ -302,7 +287,6 @@ const task = plugin(taskId, {
|
||||
component: {
|
||||
ProjectEditor: '' as AnyComponent,
|
||||
ProjectTypeSelector: '' as AnyComponent,
|
||||
TodoItemsPopup: '' as AnyComponent,
|
||||
CreateStatePopup: '' as AnyComponent
|
||||
},
|
||||
ids: {
|
||||
|
@ -627,10 +627,12 @@ export async function restore (
|
||||
Object.keys(s.domains).forEach((it) => domains.add(it as Domain))
|
||||
}
|
||||
|
||||
console.log('connecting:', transactorUrl, workspaceId.name)
|
||||
const connection = (await connect(transactorUrl, workspaceId, undefined, {
|
||||
mode: 'backup',
|
||||
model: 'upgrade'
|
||||
})) as unknown as CoreClient & BackupClient
|
||||
console.log('connected')
|
||||
|
||||
// We need to find empty domains and clean them.
|
||||
const allDomains = connection.getHierarchy().domains()
|
||||
|
@ -399,10 +399,7 @@ export class FullTextIndexPipeline implements FullTextPipeline {
|
||||
removed: false
|
||||
},
|
||||
{
|
||||
limit: globalIndexer.processingSize,
|
||||
sort: {
|
||||
_id: 1
|
||||
}
|
||||
limit: globalIndexer.processingSize
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -20,27 +20,27 @@ import core, {
|
||||
DocumentQuery,
|
||||
FindOptions,
|
||||
FindResult,
|
||||
generateId,
|
||||
LookupData,
|
||||
MeasureContext,
|
||||
ObjQueryType,
|
||||
Position,
|
||||
PullArray,
|
||||
Ref,
|
||||
SearchOptions,
|
||||
SearchQuery,
|
||||
SearchResult,
|
||||
ServerStorage,
|
||||
Space,
|
||||
systemAccountEmail,
|
||||
Tx,
|
||||
TxCreateDoc,
|
||||
TxCUD,
|
||||
TxCreateDoc,
|
||||
TxProcessor,
|
||||
TxRemoveDoc,
|
||||
TxUpdateDoc,
|
||||
TxWorkspaceEvent,
|
||||
WorkspaceEvent,
|
||||
SearchResult,
|
||||
SearchQuery,
|
||||
SearchOptions
|
||||
generateId,
|
||||
systemAccountEmail
|
||||
} from '@hcengineering/core'
|
||||
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
|
||||
import { BroadcastFunc, Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
|
||||
@ -117,16 +117,26 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
|
||||
for (const domain in classesPerDomain) {
|
||||
for (const _class of classesPerDomain[domain]) {
|
||||
const field = this.getKey(_class)
|
||||
const spaces = await this.storage.findAll(
|
||||
ctx,
|
||||
_class,
|
||||
{},
|
||||
{
|
||||
projection: { [field]: 1 }
|
||||
const map = this.domainSpaces[domain] ?? new Set()
|
||||
this.domainSpaces[domain] = map
|
||||
|
||||
while (true) {
|
||||
const spaces = await this.storage.findAll(
|
||||
ctx,
|
||||
_class,
|
||||
{
|
||||
[field]: { $nin: Array.from(map.values()) }
|
||||
},
|
||||
{
|
||||
projection: { [field]: 1 },
|
||||
limit: 1000
|
||||
}
|
||||
)
|
||||
if (spaces.length === 0) {
|
||||
break
|
||||
}
|
||||
)
|
||||
this.domainSpaces[domain] = this.domainSpaces[domain] ?? new Set()
|
||||
spaces.forEach((p) => this.domainSpaces[domain].add((p as any)[field] as Ref<Space>))
|
||||
spaces.forEach((p) => map.add((p as any)[field] as Ref<Space>))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -594,7 +594,24 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
|
||||
find (domain: Domain): StorageIterator {
|
||||
const coll = this.db.collection<Doc>(domain)
|
||||
const iterator = coll.find({}, { sort: { _id: -1 } })
|
||||
const iterator = coll.find({}, {})
|
||||
|
||||
const bulkUpdate = new Map<Ref<Doc>, string>()
|
||||
const flush = async (flush = false): Promise<void> => {
|
||||
if (bulkUpdate.size > 1000 || flush) {
|
||||
if (bulkUpdate.size > 0) {
|
||||
await coll.bulkWrite(
|
||||
Array.from(bulkUpdate.entries()).map((it) => ({
|
||||
updateOne: {
|
||||
filter: { _id: it[0] },
|
||||
update: { $set: { '%hash%': it[1] } }
|
||||
}
|
||||
}))
|
||||
)
|
||||
}
|
||||
bulkUpdate.clear()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
next: async () => {
|
||||
@ -611,7 +628,10 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
const hash = createHash('sha256')
|
||||
hash.update(doc)
|
||||
digest = hash.digest('base64')
|
||||
await coll.updateOne({ _id: d._id }, { $set: { '%hash%': digest } })
|
||||
|
||||
bulkUpdate.set(d._id, digest)
|
||||
// await coll.updateOne({ _id: d._id }, { $set: { '%hash%': digest } })
|
||||
await flush()
|
||||
}
|
||||
return {
|
||||
id: d._id,
|
||||
@ -620,6 +640,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
}
|
||||
},
|
||||
close: async () => {
|
||||
await flush(true)
|
||||
await iterator.close()
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import core, {
|
||||
FieldIndex,
|
||||
Hierarchy,
|
||||
IndexKind,
|
||||
IndexOrder,
|
||||
ModelDb,
|
||||
Tx,
|
||||
WorkspaceId
|
||||
@ -250,8 +251,12 @@ async function createUpdateIndexes (connection: CoreClient, db: Db, logger: Mode
|
||||
const attrs = hierarchy.getAllAttributes(c._id)
|
||||
const domainAttrs = domains.get(domain) ?? new Set<string | FieldIndex<Doc>>()
|
||||
for (const a of attrs.values()) {
|
||||
if (a.index !== undefined && a.index === IndexKind.Indexed) {
|
||||
domainAttrs.add(a.name)
|
||||
if (a.index !== undefined && (a.index === IndexKind.Indexed || a.index === IndexKind.IndexedDsc)) {
|
||||
if (a.index === IndexKind.Indexed) {
|
||||
domainAttrs.add(a.name)
|
||||
} else {
|
||||
domainAttrs.add({ [a.name]: IndexOrder.Descending })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,8 +154,6 @@ class TSessionManager implements SessionManager {
|
||||
return this.sessionFactory(token, pipeline, this.broadcast.bind(this))
|
||||
}
|
||||
|
||||
upgradeIdMap = new Map<string, string>()
|
||||
|
||||
async addSession (
|
||||
ctx: MeasureContext,
|
||||
ws: ConnectionSocket,
|
||||
@ -176,18 +174,14 @@ class TSessionManager implements SessionManager {
|
||||
}
|
||||
|
||||
let pipeline: Pipeline
|
||||
const upgradeId = this.upgradeIdMap.get(token.workspace.name)
|
||||
if (token.extra?.model === 'upgrade') {
|
||||
if (upgradeId !== undefined && sessionId !== upgradeId) {
|
||||
ws.close()
|
||||
throw new Error('Another Upgrade in progress....')
|
||||
if (workspace.upgrade) {
|
||||
pipeline = await ctx.with('pipeline', {}, async () => await (workspace as Workspace).pipeline)
|
||||
} else {
|
||||
pipeline = await this.createUpgradeSession(token, sessionId, ctx, wsString, workspace, pipelineFactory, ws)
|
||||
}
|
||||
if (sessionId !== undefined) {
|
||||
this.upgradeIdMap.set(token.workspace.name, sessionId)
|
||||
}
|
||||
pipeline = await this.createUpgradeSession(token, sessionId, ctx, wsString, workspace, pipelineFactory, ws)
|
||||
} else {
|
||||
if (workspace.upgrade && sessionId !== upgradeId) {
|
||||
if (workspace.upgrade) {
|
||||
ws.close()
|
||||
throw new Error('Upgrade in progress....')
|
||||
}
|
||||
@ -331,10 +325,6 @@ class TSessionManager implements SessionManager {
|
||||
}
|
||||
const sessionRef = this.sessions.get(ws.id)
|
||||
if (sessionRef !== undefined) {
|
||||
const upgradeId = this.upgradeIdMap.get(workspaceId.name)
|
||||
if (upgradeId === sessionRef.session.sessionId) {
|
||||
this.upgradeIdMap.delete(workspaceId.name)
|
||||
}
|
||||
this.sessions.delete(ws.id)
|
||||
workspace.sessions.delete(sessionRef.session.sessionId)
|
||||
this.reconnectIds.add(sessionRef.session.sessionId)
|
||||
@ -441,14 +431,12 @@ class TSessionManager implements SessionManager {
|
||||
|
||||
if (this.workspaces.get(wsid)?.id === wsUID) {
|
||||
this.workspaces.delete(wsid)
|
||||
this.upgradeIdMap.delete(workspaceId.name)
|
||||
}
|
||||
if (LOGGING_ENABLED) {
|
||||
console.timeLog(workspaceId.name, 'Closed workspace', wsUID)
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.workspaces.delete(wsid)
|
||||
this.upgradeIdMap.delete(workspaceId.name)
|
||||
if (LOGGING_ENABLED) {
|
||||
console.error(workspaceId.name, err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user