mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-31 04:38:02 +00:00
Draft tabs sync (#3030)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
67d361d856
commit
01afbab81d
@ -68,7 +68,6 @@
|
|||||||
let refInput: StyledTextBox
|
let refInput: StyledTextBox
|
||||||
|
|
||||||
let inputFile: HTMLInputElement
|
let inputFile: HTMLInputElement
|
||||||
let draftAttachments: Record<Ref<Attachment>, Attachment> | undefined = undefined
|
|
||||||
let saved = false
|
let saved = false
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
@ -78,11 +77,14 @@
|
|||||||
const newAttachments: Set<Ref<Attachment>> = new Set<Ref<Attachment>>()
|
const newAttachments: Set<Ref<Attachment>> = new Set<Ref<Attachment>>()
|
||||||
const removedAttachments: Set<Attachment> = new Set<Attachment>()
|
const removedAttachments: Set<Attachment> = new Set<Attachment>()
|
||||||
|
|
||||||
$: objectId && draftKey && updateAttachments(objectId, draftKey)
|
$: draftKey && updateAttachments(objectId, $draftsStore[draftKey])
|
||||||
|
|
||||||
async function updateAttachments (objectId: Ref<Doc>, draftKey: string) {
|
async function updateAttachments (
|
||||||
draftAttachments = $draftsStore[draftKey]
|
objectId: Ref<Doc> | undefined,
|
||||||
|
draftAttachments: Record<Ref<Attachment>, Attachment> | undefined
|
||||||
|
) {
|
||||||
if (draftAttachments && shouldSaveDraft) {
|
if (draftAttachments && shouldSaveDraft) {
|
||||||
|
query.unsubscribe()
|
||||||
attachments.clear()
|
attachments.clear()
|
||||||
newAttachments.clear()
|
newAttachments.clear()
|
||||||
Object.entries(draftAttachments).map((file) => {
|
Object.entries(draftAttachments).map((file) => {
|
||||||
@ -93,7 +95,8 @@
|
|||||||
})
|
})
|
||||||
originalAttachments.clear()
|
originalAttachments.clear()
|
||||||
removedAttachments.clear()
|
removedAttachments.clear()
|
||||||
} else {
|
attachments = attachments
|
||||||
|
} else if (objectId) {
|
||||||
query.query(
|
query.query(
|
||||||
attachment.class.Attachment,
|
attachment.class.Attachment,
|
||||||
{
|
{
|
||||||
@ -102,6 +105,7 @@
|
|||||||
(res) => {
|
(res) => {
|
||||||
originalAttachments = new Set(res.map((p) => p._id))
|
originalAttachments = new Set(res.map((p) => p._id))
|
||||||
attachments = toIdMap(res)
|
attachments = toIdMap(res)
|
||||||
|
dispatch('attach', { action: 'saved', value: attachments.size })
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -109,7 +113,7 @@
|
|||||||
|
|
||||||
async function saveDraft () {
|
async function saveDraft () {
|
||||||
if (draftKey && shouldSaveDraft) {
|
if (draftKey && shouldSaveDraft) {
|
||||||
draftAttachments = Object.fromEntries(attachments)
|
const draftAttachments = Object.fromEntries(attachments)
|
||||||
DraftController.save(draftKey, draftAttachments)
|
DraftController.save(draftKey, draftAttachments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,6 +142,7 @@
|
|||||||
attachments = attachments
|
attachments = attachments
|
||||||
saved = false
|
saved = false
|
||||||
saveDraft()
|
saveDraft()
|
||||||
|
dispatch('attach', { action: 'saved', value: attachments.size })
|
||||||
dispatch('attached', _id)
|
dispatch('attached', _id)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setPlatformStatus(unknownError(err))
|
setPlatformStatus(unknownError(err))
|
||||||
@ -255,10 +260,6 @@
|
|||||||
removedAttachments.clear()
|
removedAttachments.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (attachments.size || newAttachments.size || removedAttachments.size) {
|
|
||||||
dispatch('attach', { action: 'saved', value: attachments.size })
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAllowedPaste (evt: ClipboardEvent) {
|
function isAllowedPaste (evt: ClipboardEvent) {
|
||||||
let t: HTMLElement | null = evt.target as HTMLElement
|
let t: HTMLElement | null = evt.target as HTMLElement
|
||||||
|
|
||||||
|
@ -50,15 +50,15 @@
|
|||||||
Component,
|
Component,
|
||||||
createFocusManager,
|
createFocusManager,
|
||||||
EditBox,
|
EditBox,
|
||||||
|
IconFile as FileIcon,
|
||||||
FocusHandler,
|
FocusHandler,
|
||||||
getColorNumberByText,
|
getColorNumberByText,
|
||||||
IconFile as FileIcon,
|
|
||||||
IconInfo,
|
IconInfo,
|
||||||
Label,
|
Label,
|
||||||
showPopup,
|
showPopup,
|
||||||
Spinner
|
Spinner
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import recruit from '../plugin'
|
import recruit from '../plugin'
|
||||||
import FileUpload from './icons/FileUpload.svelte'
|
import FileUpload from './icons/FileUpload.svelte'
|
||||||
import YesNo from './YesNo.svelte'
|
import YesNo from './YesNo.svelte'
|
||||||
@ -81,10 +81,29 @@
|
|||||||
const empty = {}
|
const empty = {}
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const hierarchy = client.getHierarchy()
|
const hierarchy = client.getHierarchy()
|
||||||
const ignoreKeys = ['onsite', 'remote']
|
const ignoreKeys = ['onsite', 'remote', 'title']
|
||||||
|
|
||||||
const draft = shouldSaveDraft ? draftController.get() : undefined
|
let draft = shouldSaveDraft ? ($draftsStore[recruit.mixin.Candidate] as CandidateDraft) : undefined
|
||||||
|
$: draft = shouldSaveDraft ? ($draftsStore[recruit.mixin.Candidate] as CandidateDraft) : undefined
|
||||||
let object = draft ?? getEmptyCandidate()
|
let object = draft ?? getEmptyCandidate()
|
||||||
|
|
||||||
|
function draftChange (draft: CandidateDraft | undefined) {
|
||||||
|
if (draft === undefined) {
|
||||||
|
object = getEmptyCandidate()
|
||||||
|
} else {
|
||||||
|
object = draft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function objectChange (object: CandidateDraft, empty: any) {
|
||||||
|
if (shouldSaveDraft) {
|
||||||
|
draftController.save(object, empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: objectChange(object, empty)
|
||||||
|
$: draftChange(draft)
|
||||||
|
|
||||||
type resumeFile = {
|
type resumeFile = {
|
||||||
name: string
|
name: string
|
||||||
uuid: string
|
uuid: string
|
||||||
@ -113,12 +132,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldSaveDraft) {
|
|
||||||
draftController.watch(object, empty)
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy(() => draftController.unsubscribe())
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let inputFile: HTMLInputElement
|
let inputFile: HTMLInputElement
|
||||||
@ -643,6 +656,9 @@
|
|||||||
toClass={contact.class.Contact}
|
toClass={contact.class.Contact}
|
||||||
{ignoreKeys}
|
{ignoreKeys}
|
||||||
extraProps={{ showNavigate: false }}
|
extraProps={{ showNavigate: false }}
|
||||||
|
on:update={() => {
|
||||||
|
object = object
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
@ -55,7 +55,6 @@
|
|||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
import { ObjectBox } from '@hcengineering/view-resources'
|
import { ObjectBox } from '@hcengineering/view-resources'
|
||||||
import { onDestroy } from 'svelte'
|
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { activeComponent, activeSprint, generateIssueShortLink, getIssueId, updateIssueRelation } from '../issues'
|
import { activeComponent, activeSprint, generateIssueShortLink, getIssueId, updateIssueRelation } from '../issues'
|
||||||
import tracker from '../plugin'
|
import tracker from '../plugin'
|
||||||
@ -84,7 +83,8 @@
|
|||||||
|
|
||||||
const draftController = new DraftController<any>(tracker.ids.IssueDraft)
|
const draftController = new DraftController<any>(tracker.ids.IssueDraft)
|
||||||
|
|
||||||
const draft: IssueDraft | undefined = shouldSaveDraft ? draftController.get() : undefined
|
let draft = shouldSaveDraft ? ($draftsStore[tracker.ids.IssueDraft] as IssueDraft) : undefined
|
||||||
|
$: draft = shouldSaveDraft ? ($draftsStore[tracker.ids.IssueDraft] as IssueDraft) : undefined
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const hierarchy = client.getHierarchy()
|
const hierarchy = client.getHierarchy()
|
||||||
const parentQuery = createQuery()
|
const parentQuery = createQuery()
|
||||||
@ -92,6 +92,24 @@
|
|||||||
|
|
||||||
let object = draft ?? getDefaultObject()
|
let object = draft ?? getDefaultObject()
|
||||||
|
|
||||||
|
function draftChange (draft: IssueDraft | undefined) {
|
||||||
|
if (draft === undefined) {
|
||||||
|
object = getDefaultObject()
|
||||||
|
} else {
|
||||||
|
object = draft
|
||||||
|
descriptionBox?.setContent(object.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function objectChange (object: IssueDraft, empty: any) {
|
||||||
|
if (shouldSaveDraft) {
|
||||||
|
draftController.save(object, empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: objectChange(object, empty)
|
||||||
|
$: draftChange(draft)
|
||||||
|
|
||||||
$: if (object.parentIssue) {
|
$: if (object.parentIssue) {
|
||||||
parentQuery.query(
|
parentQuery.query(
|
||||||
tracker.class.Issue,
|
tracker.class.Issue,
|
||||||
@ -297,12 +315,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: watch(empty)
|
|
||||||
function watch (empty: Record<string, any>): void {
|
|
||||||
if (!shouldSaveDraft) return
|
|
||||||
draftController.watch(object, empty)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createIssue () {
|
async function createIssue () {
|
||||||
const _id: Ref<Issue> = generateId()
|
const _id: Ref<Issue> = generateId()
|
||||||
if (!canSave || object.status === undefined) {
|
if (!canSave || object.status === undefined) {
|
||||||
@ -390,6 +402,7 @@
|
|||||||
draftController.remove()
|
draftController.remove()
|
||||||
resetObject()
|
resetObject()
|
||||||
descriptionBox?.removeDraft(false)
|
descriptionBox?.removeDraft(false)
|
||||||
|
subIssuesComponent.removeChildDraft()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showMoreActions (ev: Event) {
|
async function showMoreActions (ev: Event) {
|
||||||
@ -503,6 +516,7 @@
|
|||||||
if (result === true) {
|
if (result === true) {
|
||||||
dispatch('close')
|
dispatch('close')
|
||||||
resetObject()
|
resetObject()
|
||||||
|
subIssuesComponent.removeChildDraft()
|
||||||
draftController.remove()
|
draftController.remove()
|
||||||
descriptionBox?.removeDraft(true)
|
descriptionBox?.removeDraft(true)
|
||||||
}
|
}
|
||||||
@ -511,8 +525,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => draftController.unsubscribe())
|
|
||||||
|
|
||||||
$: objectId = object._id
|
$: objectId = object._id
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -36,7 +36,9 @@
|
|||||||
let lastProject = project
|
let lastProject = project
|
||||||
|
|
||||||
let isCollapsed = false
|
let isCollapsed = false
|
||||||
let isCreating = $draftsStore[tracker.ids.IssueDraftChild] !== undefined
|
$: isCreatingMode = $draftsStore[tracker.ids.IssueDraftChild] !== undefined
|
||||||
|
let isManualCreating = false
|
||||||
|
$: isCreating = isCreatingMode || isManualCreating
|
||||||
|
|
||||||
async function handleIssueSwap (ev: CustomEvent<{ fromIndex: number; toIndex: number }>) {
|
async function handleIssueSwap (ev: CustomEvent<{ fromIndex: number; toIndex: number }>) {
|
||||||
if (subIssues) {
|
if (subIssues) {
|
||||||
@ -170,7 +172,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removeChildDraft () {
|
||||||
|
draftChild?.removeDraft()
|
||||||
|
}
|
||||||
|
|
||||||
$: hasSubIssues = subIssues.length > 0
|
$: hasSubIssues = subIssues.length > 0
|
||||||
|
|
||||||
|
let draftChild: DraftIssueChildEditor
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-between clear-mins">
|
<div class="flex-between clear-mins">
|
||||||
@ -200,7 +208,7 @@
|
|||||||
showTooltip={{ label: tracker.string.AddSubIssues, props: { subIssues: 1 } }}
|
showTooltip={{ label: tracker.string.AddSubIssues, props: { subIssues: 1 } }}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
closeTooltip()
|
closeTooltip()
|
||||||
isCreating = true
|
isManualCreating = true
|
||||||
isCollapsed = false
|
isCollapsed = false
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -224,12 +232,13 @@
|
|||||||
{#if isCreating && project}
|
{#if isCreating && project}
|
||||||
<ExpandCollapse isExpanded={!isCollapsed} on:changeContent>
|
<ExpandCollapse isExpanded={!isCollapsed} on:changeContent>
|
||||||
<DraftIssueChildEditor
|
<DraftIssueChildEditor
|
||||||
|
bind:this={draftChild}
|
||||||
{project}
|
{project}
|
||||||
{component}
|
{component}
|
||||||
{sprint}
|
{sprint}
|
||||||
{shouldSaveDraft}
|
{shouldSaveDraft}
|
||||||
on:close={() => {
|
on:close={() => {
|
||||||
isCreating = false
|
isManualCreating = false
|
||||||
}}
|
}}
|
||||||
on:create={(evt) => {
|
on:create={(evt) => {
|
||||||
if (subIssues === undefined) {
|
if (subIssues === undefined) {
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact, { Employee, EmployeeAccount } from '@hcengineering/contact'
|
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||||
import { AssigneeBox } from '@hcengineering/contact-resources'
|
import { AssigneeBox, employeeAccountByIdStore } from '@hcengineering/contact-resources'
|
||||||
import { AttachedData, Ref } from '@hcengineering/core'
|
import { AttachedData, Ref } from '@hcengineering/core'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { Issue, IssueDraft, IssueTemplateData } from '@hcengineering/tracker'
|
import { Issue, IssueDraft, IssueTemplateData } from '@hcengineering/tracker'
|
||||||
@ -58,9 +58,9 @@
|
|||||||
if (hasSpace(issue)) {
|
if (hasSpace(issue)) {
|
||||||
const project = await client.findOne(tracker.class.Project, { _id: issue.space })
|
const project = await client.findOne(tracker.class.Project, { _id: issue.space })
|
||||||
if (project !== undefined) {
|
if (project !== undefined) {
|
||||||
const accounts = await client.findAll(contact.class.EmployeeAccount, {
|
const accounts = project.members
|
||||||
_id: { $in: project.members as Ref<EmployeeAccount>[] }
|
.map((p) => $employeeAccountByIdStore.get(p as Ref<EmployeeAccount>))
|
||||||
})
|
.filter((p) => p !== undefined) as EmployeeAccount[]
|
||||||
members = accounts.map((p) => p.employee)
|
members = accounts.map((p) => p.employee)
|
||||||
} else {
|
} else {
|
||||||
members = []
|
members = []
|
||||||
|
@ -15,11 +15,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
|
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
|
||||||
import { Account, Doc, generateId, Ref } from '@hcengineering/core'
|
import { Account, Doc, generateId, Ref } from '@hcengineering/core'
|
||||||
import presentation, { DraftController, getClient, KeyedAttribute } from '@hcengineering/presentation'
|
import presentation, { DraftController, draftsStore, getClient, KeyedAttribute } from '@hcengineering/presentation'
|
||||||
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
||||||
import { Component as ComponentType, IssueDraft, IssuePriority, Project, Sprint } from '@hcengineering/tracker'
|
import { Component as ComponentType, IssueDraft, IssuePriority, Project, Sprint } from '@hcengineering/tracker'
|
||||||
import { Button, Component, EditBox } from '@hcengineering/ui'
|
import { Button, Component, EditBox } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import AssigneeEditor from '../issues/AssigneeEditor.svelte'
|
import AssigneeEditor from '../issues/AssigneeEditor.svelte'
|
||||||
import PriorityEditor from '../issues/PriorityEditor.svelte'
|
import PriorityEditor from '../issues/PriorityEditor.svelte'
|
||||||
@ -36,17 +36,28 @@
|
|||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const draftController = new DraftController<IssueDraft>(tracker.ids.IssueDraftChild)
|
const draftController = new DraftController<IssueDraft>(tracker.ids.IssueDraftChild)
|
||||||
const draft = shouldSaveDraft ? draftController.get() : undefined
|
const draft = shouldSaveDraft ? ($draftsStore[tracker.ids.IssueDraftChild] as IssueDraft) : undefined
|
||||||
let object = childIssue !== undefined ? childIssue : draft ?? getIssueDefaults()
|
let object = childIssue !== undefined ? childIssue : draft ?? getIssueDefaults()
|
||||||
let thisRef: HTMLDivElement
|
let thisRef: HTMLDivElement
|
||||||
let focusIssueTitle: () => void
|
let focusIssueTitle: () => void
|
||||||
onDestroy(() => draftController.unsubscribe())
|
|
||||||
|
|
||||||
const key: KeyedAttribute = {
|
const key: KeyedAttribute = {
|
||||||
key: 'labels',
|
key: 'labels',
|
||||||
attr: client.getHierarchy().getAttribute(tracker.class.IssueTemplate, '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 {
|
function getIssueDefaults (): IssueDraft {
|
||||||
return {
|
return {
|
||||||
_id: generateId(),
|
_id: generateId(),
|
||||||
@ -75,10 +86,14 @@
|
|||||||
sprint
|
sprint
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldSaveDraft) {
|
function objectChange (object: IssueDraft, empty: any) {
|
||||||
draftController.watch(object, empty)
|
if (shouldSaveDraft) {
|
||||||
|
draftController.save(object, empty)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: objectChange(object, empty)
|
||||||
|
|
||||||
function resetToDefaults () {
|
function resetToDefaults () {
|
||||||
object = getIssueDefaults()
|
object = getIssueDefaults()
|
||||||
focusIssueTitle?.()
|
focusIssueTitle?.()
|
||||||
@ -88,8 +103,12 @@
|
|||||||
return value.trim()
|
return value.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function close () {
|
export function removeDraft () {
|
||||||
draftController.remove()
|
draftController.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
function close () {
|
||||||
|
removeDraft()
|
||||||
dispatch('close')
|
dispatch('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +119,7 @@
|
|||||||
|
|
||||||
dispatch(childIssue ? 'close' : 'create', object)
|
dispatch(childIssue ? 'close' : 'create', object)
|
||||||
|
|
||||||
draftController.remove()
|
removeDraft()
|
||||||
resetToDefaults()
|
resetToDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +168,7 @@
|
|||||||
<div class="mt-4 clear-mins" id="sub-issue-description">
|
<div class="mt-4 clear-mins" id="sub-issue-description">
|
||||||
{#key objectId}
|
{#key objectId}
|
||||||
<AttachmentStyledBox
|
<AttachmentStyledBox
|
||||||
|
bind:this={descriptionBox}
|
||||||
objectId={object._id}
|
objectId={object._id}
|
||||||
space={project._id}
|
space={project._id}
|
||||||
_class={tracker.class.Issue}
|
_class={tracker.class.Issue}
|
||||||
|
Loading…
Reference in New Issue
Block a user