platform/plugins/tracker-resources/src/components/templates/DraftIssueChildEditor.svelte
Denis Bykhov 01afbab81d
Draft tabs sync (#3030)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
2023-04-21 09:50:32 +07:00

262 lines
7.5 KiB
Svelte

<!--
// 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 { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import { Account, Doc, generateId, Ref } from '@hcengineering/core'
import presentation, { DraftController, draftsStore, getClient, KeyedAttribute } from '@hcengineering/presentation'
import tags, { TagElement, TagReference } from '@hcengineering/tags'
import { Component as ComponentType, IssueDraft, IssuePriority, Project, Sprint } from '@hcengineering/tracker'
import { Button, Component, EditBox } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import AssigneeEditor from '../issues/AssigneeEditor.svelte'
import PriorityEditor from '../issues/PriorityEditor.svelte'
import StatusEditor from '../issues/StatusEditor.svelte'
import EstimationEditor from './EstimationEditor.svelte'
export let project: Project
export let sprint: Ref<Sprint> | null = null
export let component: Ref<ComponentType> | null = null
export let childIssue: IssueDraft | undefined = undefined
export let showBorder = false
export let shouldSaveDraft: boolean = false
const dispatch = createEventDispatcher()
const client = getClient()
const draftController = new DraftController<IssueDraft>(tracker.ids.IssueDraftChild)
const draft = shouldSaveDraft ? ($draftsStore[tracker.ids.IssueDraftChild] as IssueDraft) : undefined
let object = childIssue !== undefined ? childIssue : draft ?? getIssueDefaults()
let thisRef: HTMLDivElement
let focusIssueTitle: () => void
const key: KeyedAttribute = {
key: 'labels',
attr: client.getHierarchy().getAttribute(tracker.class.IssueTemplate, 'labels')
}
let descriptionBox: AttachmentStyledBox
function draftChange (draft: IssueDraft | undefined) {
if (draft === undefined) {
object = childIssue !== undefined ? childIssue : getIssueDefaults()
} else {
object = draft
descriptionBox?.setContent(object.description)
}
}
$: shouldSaveDraft && draftChange($draftsStore[tracker.ids.IssueDraftChild])
function getIssueDefaults (): IssueDraft {
return {
_id: generateId(),
title: '',
description: '',
assignee: project.defaultAssignee ?? null,
status: project.defaultIssueStatus,
space: project._id,
dueDate: null,
subIssues: [],
attachments: 0,
labels: [],
component,
priority: IssuePriority.NoPriority,
sprint,
estimation: 0
}
}
const empty = {
space: project._id,
status: project.defaultIssueStatus,
assignee: project.defaultAssignee ?? null,
component,
priority: IssuePriority.NoPriority,
sprint
}
function objectChange (object: IssueDraft, empty: any) {
if (shouldSaveDraft) {
draftController.save(object, empty)
}
}
$: objectChange(object, empty)
function resetToDefaults () {
object = getIssueDefaults()
focusIssueTitle?.()
}
function getTitle (value: string) {
return value.trim()
}
export function removeDraft () {
draftController.remove()
}
function close () {
removeDraft()
dispatch('close')
}
async function createIssue () {
if (!canSave) {
return
}
dispatch(childIssue ? 'close' : 'create', object)
removeDraft()
resetToDefaults()
}
function tagAsRef (tag: TagElement): TagReference {
return {
_class: tags.class.TagReference,
_id: generateId() as Ref<TagReference>,
attachedTo: '' as Ref<Doc>,
attachedToClass: tracker.class.Issue,
collection: 'labels',
space: tags.space.Tags,
modifiedOn: 0,
modifiedBy: '' as Ref<Account>,
title: tag.title,
tag: tag._id,
color: tag.color
}
}
function addTagRef (tag: TagElement): void {
object.labels = [...object.labels, tagAsRef(tag)]
}
$: thisRef && thisRef.scrollIntoView({ behavior: 'smooth' })
$: canSave = getTitle(object.title ?? '').length > 0
$: objectId = object._id
</script>
<div
id="sub-issue-child-editor"
bind:this={thisRef}
class="flex-col antiEmphasized clear-mins"
class:antiPopup={showBorder}
>
<div class="flex-col w-full clear-mins">
<div id="sub-issue-name">
<EditBox
bind:value={object.title}
bind:focusInput={focusIssueTitle}
kind={'large-style'}
placeholder={tracker.string.SubIssueTitlePlaceholder}
focus
/>
</div>
<div class="mt-4 clear-mins" id="sub-issue-description">
{#key objectId}
<AttachmentStyledBox
bind:this={descriptionBox}
objectId={object._id}
space={project._id}
_class={tracker.class.Issue}
bind:content={object.description}
placeholder={tracker.string.SubIssueDescriptionPlaceholder}
showButtons={false}
alwaysEdit
shouldSaveDraft
maxHeight={'limited'}
on:changeContent
/>
{/key}
</div>
</div>
<div class="mt-4 flex-between">
<div class="buttons-group xsmall-gap">
<div id="sub-issue-status-editor">
<StatusEditor
value={object}
kind="no-border"
size="small"
shouldShowLabel={true}
on:change={({ detail }) => (object.status = detail)}
/>
</div>
<div id="sub-issue-priority-editor">
<PriorityEditor
value={object}
shouldShowLabel
isEditable
kind="no-border"
size="small"
justify="center"
on:change={({ detail }) => (object.priority = detail)}
/>
</div>
<div id="sub-issue-assignee-editor">
{#key object.assignee}
<AssigneeEditor
value={object}
size="small"
kind="no-border"
on:change={({ detail }) => (object.assignee = detail)}
/>
{/key}
</div>
<div id="sub-issue-estimation-editor">
<EstimationEditor
kind={'no-border'}
size={'small'}
bind:value={object}
on:change={(evt) => {
object.estimation = evt.detail
}}
/>
</div>
<Component
is={tags.component.TagsDropdownEditor}
props={{
items: object.labels,
key,
targetClass: tracker.class.Issue,
countLabel: tracker.string.NumberLabels
}}
on:open={(evt) => {
addTagRef(evt.detail)
}}
on:delete={(evt) => {
object.labels = object.labels.filter((it) => it._id !== evt.detail)
}}
/>
</div>
<div class="buttons-group small-gap">
<Button label={presentation.string.Cancel} size="small" kind="transparent" on:click={close} />
<Button
disabled={!canSave}
label={presentation.string.Save}
size="small"
kind="no-border"
on:click={createIssue}
/>
</div>
</div>
</div>
<style lang="scss">
.antiPopup {
max-width: 100%;
}
</style>