mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
TSK-1335 Improve draft (#3092)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
97396cc5f2
commit
be1872abe5
@ -1,20 +1,115 @@
|
||||
import { fetchMetadataLocalStorage, setMetadataLocalStorage } from '@hcengineering/ui'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { get, writable } from 'svelte/store'
|
||||
import { Unsubscriber, writable } from 'svelte/store'
|
||||
import presentation from './plugin'
|
||||
|
||||
migrateDrafts()
|
||||
export const draftsStore = writable<Record<string, any>>(fetchMetadataLocalStorage(presentation.metadata.Draft) ?? {})
|
||||
let drafts: Record<string, any> = fetchMetadataLocalStorage(presentation.metadata.Draft) ?? {}
|
||||
const activeDraftsKey = 'activeDrafts'
|
||||
export const activeDraftsStore = writable<Set<string>>(new Set())
|
||||
const activeDrafts: Set<string> = new Set()
|
||||
|
||||
window.addEventListener('storage', storageHandler)
|
||||
const saveInterval = 200
|
||||
|
||||
function storageHandler (evt: StorageEvent): void {
|
||||
if (evt.storageArea !== localStorage) return
|
||||
if (evt.key !== presentation.metadata.Draft) return
|
||||
if (evt.newValue !== null) {
|
||||
draftsStore.set(JSON.parse(evt.newValue))
|
||||
if (evt.storageArea === localStorage) {
|
||||
if (evt.key !== presentation.metadata.Draft) return
|
||||
if (evt.newValue !== null) {
|
||||
drafts = JSON.parse(evt.newValue)
|
||||
draftsStore.set(drafts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function syncDrafts (): void {
|
||||
draftsStore.set(drafts)
|
||||
setMetadataLocalStorage(presentation.metadata.Draft, drafts)
|
||||
}
|
||||
|
||||
// #region Broadcast
|
||||
|
||||
const bc = new BroadcastChannel(activeDraftsKey)
|
||||
|
||||
type BroadcastMessage = BroadcastGetMessage | BroadcastGetResp | BroadcastAddMessage | BroadcastRemoveMessage
|
||||
|
||||
interface BroadcastGetMessage {
|
||||
type: 'get_all'
|
||||
}
|
||||
|
||||
interface BroadcastGetResp {
|
||||
type: 'get_all_response'
|
||||
value: string[]
|
||||
}
|
||||
|
||||
interface BroadcastRemoveMessage {
|
||||
type: 'remove'
|
||||
value: string
|
||||
}
|
||||
|
||||
interface BroadcastAddMessage {
|
||||
type: 'add'
|
||||
value: string
|
||||
}
|
||||
|
||||
function sendMessage (req: BroadcastMessage): void {
|
||||
bc.postMessage(req)
|
||||
}
|
||||
|
||||
function syncActive (): void {
|
||||
activeDraftsStore.set(activeDrafts)
|
||||
}
|
||||
|
||||
function loadActiveDrafts (): void {
|
||||
activeDrafts.clear()
|
||||
syncActive()
|
||||
sendMessage({ type: 'get_all' })
|
||||
}
|
||||
|
||||
bc.onmessage = (e: MessageEvent<BroadcastMessage>) => {
|
||||
if (e.data.type === 'get_all') {
|
||||
sendMessage({ type: 'get_all_response', value: Array.from(activeDrafts.values()) })
|
||||
}
|
||||
if (e.data.type === 'get_all_response') {
|
||||
for (const val of e.data.value) {
|
||||
activeDrafts.add(val)
|
||||
}
|
||||
syncActive()
|
||||
}
|
||||
if (e.data.type === 'add') {
|
||||
activeDrafts.add(e.data.value)
|
||||
syncActive()
|
||||
}
|
||||
if (e.data.type === 'remove') {
|
||||
activeDrafts.delete(e.data.value)
|
||||
syncActive()
|
||||
}
|
||||
}
|
||||
|
||||
loadActiveDrafts()
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Active
|
||||
|
||||
function addActive (id: string): void {
|
||||
if (!activeDrafts.has(id)) {
|
||||
activeDrafts.add(id)
|
||||
syncActive()
|
||||
sendMessage({ type: 'add', value: id })
|
||||
}
|
||||
}
|
||||
|
||||
function deleteActive (id: string): void {
|
||||
if (activeDrafts.has(id)) {
|
||||
activeDrafts.delete(id)
|
||||
syncActive()
|
||||
sendMessage({ type: 'remove', value: id })
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
function isEmptyDraft<T> (object: T, emptyObj: Partial<T> | undefined): boolean {
|
||||
for (const key in object) {
|
||||
if (key === '_id') continue
|
||||
@ -44,73 +139,161 @@ function isEmptyDraft<T> (object: T, emptyObj: Partial<T> | undefined): boolean
|
||||
return true
|
||||
}
|
||||
|
||||
function removeDraft (id: string, parentId: string | undefined = undefined): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete drafts[id]
|
||||
deleteActive(id)
|
||||
syncDrafts()
|
||||
if (parentId !== undefined) {
|
||||
MultipleDraftController.remove(parentId, id)
|
||||
}
|
||||
}
|
||||
|
||||
export class DraftController<T> {
|
||||
private timer: number | undefined = undefined
|
||||
constructor (private readonly id: string) {}
|
||||
private unsub: Unsubscriber | undefined = undefined
|
||||
constructor (private readonly id: string | undefined, private readonly parentId: string | undefined = undefined) {
|
||||
if (this.id !== undefined) {
|
||||
addActive(this.id)
|
||||
}
|
||||
}
|
||||
|
||||
static remove (id: string): void {
|
||||
const drafts = get(draftsStore)
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete drafts[id]
|
||||
draftsStore.set(drafts)
|
||||
setMetadataLocalStorage(presentation.metadata.Draft, drafts)
|
||||
removeDraft(id)
|
||||
}
|
||||
|
||||
static save<T>(id: string, object: T, emptyObj: Partial<T> | undefined = undefined): void {
|
||||
const drafts = get(draftsStore)
|
||||
if (emptyObj !== undefined && isEmptyDraft(object, emptyObj)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete drafts[id]
|
||||
} else {
|
||||
drafts[id] = object
|
||||
return DraftController.remove(id)
|
||||
}
|
||||
drafts[id] = object
|
||||
addActive(id)
|
||||
syncDrafts()
|
||||
}
|
||||
|
||||
destroy (): void {
|
||||
this.unsub?.()
|
||||
if (this.id !== undefined) {
|
||||
deleteActive(this.id)
|
||||
}
|
||||
}
|
||||
|
||||
subscribe (callback: (val: T | undefined) => void): Unsubscriber {
|
||||
callback(this.get())
|
||||
this.unsub = draftsStore.subscribe((p) => {
|
||||
callback(this.getValue(p))
|
||||
})
|
||||
return () => {
|
||||
this.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
private getValue (store: Record<string, any>): T | undefined {
|
||||
if (this.id !== undefined) {
|
||||
const res = store[this.id]
|
||||
return res
|
||||
}
|
||||
draftsStore.set(drafts)
|
||||
setMetadataLocalStorage(presentation.metadata.Draft, drafts)
|
||||
}
|
||||
|
||||
get (): T | undefined {
|
||||
const drafts = get(draftsStore)
|
||||
const res = drafts[this.id]
|
||||
return res
|
||||
return this.getValue(drafts)
|
||||
}
|
||||
|
||||
save (object: T, emptyObj: Partial<T> | undefined = undefined): void {
|
||||
const drafts = get(draftsStore)
|
||||
if (emptyObj !== undefined && isEmptyDraft(object, emptyObj)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete drafts[this.id]
|
||||
} else {
|
||||
return this.remove()
|
||||
}
|
||||
if (this.id !== undefined) {
|
||||
drafts[this.id] = object
|
||||
syncDrafts()
|
||||
addActive(this.id)
|
||||
if (this.parentId !== undefined) {
|
||||
MultipleDraftController.add(this.parentId, this.id)
|
||||
}
|
||||
}
|
||||
draftsStore.set(drafts)
|
||||
setMetadataLocalStorage(presentation.metadata.Draft, drafts)
|
||||
}
|
||||
|
||||
private update (object: T, emptyObj: Partial<T> | undefined): void {
|
||||
this.timer = window.setTimeout(() => {
|
||||
this.save(object, emptyObj)
|
||||
this.update(object, emptyObj)
|
||||
}, saveInterval)
|
||||
}
|
||||
|
||||
unsubscribe (): void {
|
||||
if (this?.timer !== undefined) {
|
||||
clearTimeout(this.timer)
|
||||
}
|
||||
}
|
||||
|
||||
watch (object: T, emptyObj: Partial<T> | undefined = undefined): void {
|
||||
this.unsubscribe()
|
||||
this.save(object, emptyObj)
|
||||
this.update(object, emptyObj)
|
||||
}
|
||||
|
||||
remove (): void {
|
||||
this.unsubscribe()
|
||||
const drafts = get(draftsStore)
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete drafts[this.id]
|
||||
setMetadataLocalStorage(presentation.metadata.Draft, drafts)
|
||||
draftsStore.set(drafts)
|
||||
if (this.id !== undefined) {
|
||||
removeDraft(this.id, this.parentId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MultipleDraftController {
|
||||
constructor (private readonly id: string) {}
|
||||
|
||||
static remove (id: string, value: string): void {
|
||||
const arr: string[] = drafts[id] ?? []
|
||||
const index = arr.findIndex((p) => p === value)
|
||||
if (index !== -1) {
|
||||
arr.splice(index, 1)
|
||||
drafts[id] = arr
|
||||
syncDrafts()
|
||||
}
|
||||
}
|
||||
|
||||
static add (id: string, value: string): void {
|
||||
const arr: string[] = drafts[id] ?? []
|
||||
if (!arr.includes(value)) {
|
||||
arr.push(value)
|
||||
}
|
||||
drafts[id] = arr
|
||||
syncDrafts()
|
||||
}
|
||||
|
||||
getNext (): string | undefined {
|
||||
const value = drafts[this.id] ?? []
|
||||
for (const val of value) {
|
||||
if (!activeDrafts.has(val)) {
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hasNext (callback: (value: boolean) => void): Unsubscriber {
|
||||
const next = this.getNext()
|
||||
// eslint-disable-next-line
|
||||
callback(next !== undefined)
|
||||
const draftSub = draftsStore.subscribe((drafts) => {
|
||||
const value = drafts[this.id] ?? []
|
||||
for (const val of value) {
|
||||
if (!activeDrafts.has(val)) {
|
||||
// eslint-disable-next-line
|
||||
callback(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
callback(false)
|
||||
})
|
||||
const activeSub = activeDraftsStore.subscribe((activeDrafts) => {
|
||||
const value = drafts[this.id] ?? []
|
||||
for (const val of value) {
|
||||
if (!activeDrafts.has(val)) {
|
||||
// eslint-disable-next-line
|
||||
callback(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
callback(false)
|
||||
})
|
||||
return () => {
|
||||
draftSub()
|
||||
activeSub()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function migrateDrafts (): void {
|
||||
const drafts = fetchMetadataLocalStorage(presentation.metadata.Draft) ?? {}
|
||||
const issues = drafts['tracker:ids:IssueDraft']
|
||||
if (!Array.isArray(issues)) {
|
||||
drafts['tracker:ids:IssueDraft'] = []
|
||||
}
|
||||
const candidates = drafts['recruit:mixin:Candidate']
|
||||
if (!Array.isArray(candidates)) {
|
||||
drafts['recruit:mixin:Candidate'] = []
|
||||
}
|
||||
setMetadataLocalStorage(presentation.metadata.Draft, drafts)
|
||||
}
|
||||
|
@ -14,13 +14,12 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { Comment } from '@hcengineering/chunter'
|
||||
import { AttachedData, Doc, generateId, Ref } from '@hcengineering/core'
|
||||
import { DraftController, draftsStore, getClient } from '@hcengineering/presentation'
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
import chunter from '../plugin'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
export let object: Doc
|
||||
export let shouldSaveDraft: boolean = true
|
||||
@ -44,11 +43,13 @@
|
||||
let _id: Ref<Comment> = comment._id
|
||||
let inputContent: string = comment.message
|
||||
|
||||
if (shouldSaveDraft) {
|
||||
draftController.watch(comment, empty)
|
||||
function objectChange (object: CommentDraft, empty: any) {
|
||||
if (shouldSaveDraft) {
|
||||
draftController.save(object, empty)
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(draftController.unsubscribe)
|
||||
$: objectChange(comment, empty)
|
||||
|
||||
function getDefault (): CommentDraft {
|
||||
return {
|
||||
|
@ -35,11 +35,11 @@
|
||||
Card,
|
||||
createQuery,
|
||||
DraftController,
|
||||
draftsStore,
|
||||
getClient,
|
||||
InlineAttributeBar,
|
||||
KeyedAttribute,
|
||||
MessageBox,
|
||||
MultipleDraftController,
|
||||
PDFViewer
|
||||
} from '@hcengineering/presentation'
|
||||
import type { Candidate, CandidateDraft } from '@hcengineering/recruit'
|
||||
@ -53,23 +53,28 @@
|
||||
IconFile as FileIcon,
|
||||
FocusHandler,
|
||||
getColorNumberByText,
|
||||
IconAttachment,
|
||||
IconInfo,
|
||||
Label,
|
||||
showPopup,
|
||||
Spinner,
|
||||
IconAttachment
|
||||
Spinner
|
||||
} from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import recruit from '../plugin'
|
||||
import YesNo from './YesNo.svelte'
|
||||
|
||||
export let shouldSaveDraft: boolean = false
|
||||
|
||||
const draftController = new DraftController<CandidateDraft>(recruit.mixin.Candidate)
|
||||
const mDraftController = new MultipleDraftController(recruit.mixin.Candidate)
|
||||
const id: Ref<Candidate> = generateId()
|
||||
const draftController = new DraftController<CandidateDraft>(
|
||||
shouldSaveDraft ? mDraftController.getNext() ?? id : undefined,
|
||||
recruit.mixin.Candidate
|
||||
)
|
||||
|
||||
function getEmptyCandidate (): CandidateDraft {
|
||||
function getEmptyCandidate (id: Ref<Candidate> | undefined = undefined): CandidateDraft {
|
||||
return {
|
||||
_id: generateId(),
|
||||
_id: id ?? generateId(),
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
title: '',
|
||||
@ -83,17 +88,13 @@
|
||||
const hierarchy = client.getHierarchy()
|
||||
const ignoreKeys = ['onsite', 'remote', 'title']
|
||||
|
||||
let draft = shouldSaveDraft ? ($draftsStore[recruit.mixin.Candidate] as CandidateDraft) : undefined
|
||||
$: draft = shouldSaveDraft ? ($draftsStore[recruit.mixin.Candidate] as CandidateDraft) : undefined
|
||||
let object = draft ?? getEmptyCandidate()
|
||||
|
||||
function draftChange (draft: CandidateDraft | undefined) {
|
||||
if (draft === undefined) {
|
||||
object = getEmptyCandidate()
|
||||
} else {
|
||||
object = draft
|
||||
}
|
||||
}
|
||||
let draft = shouldSaveDraft ? draftController.get() : undefined
|
||||
let object = draft ?? getEmptyCandidate(id)
|
||||
onDestroy(
|
||||
draftController.subscribe((val) => {
|
||||
draft = shouldSaveDraft ? val : undefined
|
||||
})
|
||||
)
|
||||
|
||||
function objectChange (object: CandidateDraft, empty: any) {
|
||||
if (shouldSaveDraft) {
|
||||
@ -102,7 +103,6 @@
|
||||
}
|
||||
|
||||
$: objectChange(object, empty)
|
||||
$: draftChange(draft)
|
||||
|
||||
type resumeFile = {
|
||||
name: string
|
||||
@ -481,7 +481,7 @@
|
||||
|
||||
async function showConfirmationDialog () {
|
||||
draftController.save(object, empty)
|
||||
const isFormEmpty = $draftsStore[recruit.mixin.Candidate] === undefined
|
||||
const isFormEmpty = draft === undefined
|
||||
|
||||
if (isFormEmpty) {
|
||||
dispatch('close')
|
||||
|
@ -13,12 +13,20 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { draftsStore } from '@hcengineering/presentation'
|
||||
import { Button, showPopup, IconAdd } from '@hcengineering/ui'
|
||||
import { MultipleDraftController } from '@hcengineering/presentation'
|
||||
import { Button, IconAdd, showPopup } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import recruit from '../plugin'
|
||||
import CreateCandidate from './CreateCandidate.svelte'
|
||||
|
||||
$: draftExists = $draftsStore[recruit.mixin.Candidate]
|
||||
let draftExists = false
|
||||
|
||||
const draftController = new MultipleDraftController(recruit.mixin.Candidate)
|
||||
onDestroy(
|
||||
draftController.hasNext((res) => {
|
||||
draftExists = res
|
||||
})
|
||||
)
|
||||
|
||||
async function newCandidate (): Promise<void> {
|
||||
showPopup(CreateCandidate, { shouldSaveDraft: true }, 'top')
|
||||
|
@ -22,10 +22,10 @@
|
||||
Card,
|
||||
createQuery,
|
||||
DraftController,
|
||||
draftsStore,
|
||||
getClient,
|
||||
KeyedAttribute,
|
||||
MessageBox,
|
||||
MultipleDraftController,
|
||||
SpaceSelector
|
||||
} from '@hcengineering/presentation'
|
||||
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
||||
@ -57,7 +57,7 @@
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { ObjectBox } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { activeComponent, activeSprint, generateIssueShortLink, getIssueId, updateIssueRelation } from '../issues'
|
||||
import tracker from '../plugin'
|
||||
import ComponentSelector from './ComponentSelector.svelte'
|
||||
@ -83,25 +83,26 @@
|
||||
export let parentIssue: Issue | undefined
|
||||
export let originalIssue: Issue | undefined
|
||||
|
||||
const draftController = new DraftController<any>(tracker.ids.IssueDraft)
|
||||
const mDraftController = new MultipleDraftController(tracker.ids.IssueDraft)
|
||||
const id: Ref<Issue> = generateId()
|
||||
const draftController = new DraftController<IssueDraft>(
|
||||
shouldSaveDraft ? mDraftController.getNext() ?? id : undefined,
|
||||
tracker.ids.IssueDraft
|
||||
)
|
||||
|
||||
let draft = shouldSaveDraft ? ($draftsStore[tracker.ids.IssueDraft] as IssueDraft) : undefined
|
||||
$: draft = shouldSaveDraft ? ($draftsStore[tracker.ids.IssueDraft] as IssueDraft) : undefined
|
||||
let draft = shouldSaveDraft ? draftController.get() : undefined
|
||||
|
||||
onDestroy(
|
||||
draftController.subscribe((val) => {
|
||||
draft = shouldSaveDraft ? val : undefined
|
||||
})
|
||||
)
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const parentQuery = createQuery()
|
||||
let _space = space
|
||||
|
||||
let object = draft ?? getDefaultObject()
|
||||
|
||||
function draftChange (draft: IssueDraft | undefined) {
|
||||
if (draft === undefined) {
|
||||
object = getDefaultObject()
|
||||
} else {
|
||||
object = draft
|
||||
descriptionBox?.setContent(object.description)
|
||||
}
|
||||
}
|
||||
let object = draft ?? getDefaultObject(id)
|
||||
|
||||
function objectChange (object: IssueDraft, empty: any) {
|
||||
if (shouldSaveDraft) {
|
||||
@ -110,7 +111,6 @@
|
||||
}
|
||||
|
||||
$: objectChange(object, empty)
|
||||
$: draftChange(draft)
|
||||
|
||||
$: if (object.parentIssue) {
|
||||
parentQuery.query(
|
||||
@ -127,9 +127,9 @@
|
||||
parentIssue = undefined
|
||||
}
|
||||
|
||||
function getDefaultObject (ignoreOriginal = false): IssueDraft {
|
||||
function getDefaultObject (id: Ref<Issue> | undefined = undefined, ignoreOriginal = false): IssueDraft {
|
||||
const base: IssueDraft = {
|
||||
_id: generateId(),
|
||||
_id: id ?? generateId(),
|
||||
title: '',
|
||||
description: '',
|
||||
priority,
|
||||
@ -199,7 +199,7 @@
|
||||
function resetObject (): void {
|
||||
templateId = undefined
|
||||
template = undefined
|
||||
object = getDefaultObject(true)
|
||||
object = getDefaultObject(undefined, true)
|
||||
fillDefaults(hierarchy, object, tracker.class.Issue)
|
||||
}
|
||||
|
||||
@ -502,7 +502,7 @@
|
||||
|
||||
async function showConfirmationDialog () {
|
||||
draftController.save(object, empty)
|
||||
const isFormEmpty = $draftsStore[tracker.ids.IssueDraft] === undefined
|
||||
const isFormEmpty = draft === undefined
|
||||
|
||||
if (isFormEmpty) {
|
||||
dispatch('close')
|
||||
@ -625,6 +625,7 @@
|
||||
<SubIssues
|
||||
bind:this={subIssuesComponent}
|
||||
projectId={_space}
|
||||
parendIssueId={object._id}
|
||||
project={currentProject}
|
||||
sprint={object.sprint}
|
||||
component={object.component}
|
||||
@ -638,7 +639,7 @@
|
||||
value={object}
|
||||
kind={'secondary'}
|
||||
size={'large'}
|
||||
defaultIssueStatus={draft ? undefined : currentProject?.defaultIssueStatus}
|
||||
defaultIssueStatus={currentProject?.defaultIssueStatus}
|
||||
shouldShowLabel={true}
|
||||
on:refocus={() => {
|
||||
manager.setFocusPos(3)
|
||||
|
@ -14,8 +14,9 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref, Space } from '@hcengineering/core'
|
||||
import { draftsStore, getClient } from '@hcengineering/presentation'
|
||||
import { Button, showPopup, IconAdd } from '@hcengineering/ui'
|
||||
import { MultipleDraftController, getClient } from '@hcengineering/presentation'
|
||||
import { Button, IconAdd, showPopup } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import tracker from '../plugin'
|
||||
import CreateIssue from './CreateIssue.svelte'
|
||||
|
||||
@ -24,10 +25,17 @@
|
||||
const client = getClient()
|
||||
|
||||
let space: Ref<Space> | undefined
|
||||
let closed = true
|
||||
$: updateSpace(currentSpace)
|
||||
|
||||
$: draftExists =
|
||||
$draftsStore[tracker.ids.IssueDraft] !== undefined || $draftsStore[tracker.ids.IssueDraftChild] !== undefined
|
||||
let draftExists = false
|
||||
|
||||
const draftController = new MultipleDraftController(tracker.ids.IssueDraft)
|
||||
onDestroy(
|
||||
draftController.hasNext((res) => {
|
||||
draftExists = res
|
||||
})
|
||||
)
|
||||
|
||||
async function updateSpace (spaceId: Ref<Space> | undefined): Promise<void> {
|
||||
if (spaceId !== undefined) {
|
||||
@ -44,15 +52,15 @@
|
||||
const project = await client.findOne(tracker.class.Project, {})
|
||||
space = project?._id
|
||||
}
|
||||
|
||||
showPopup(CreateIssue, { space, shouldSaveDraft: true }, 'top')
|
||||
closed = false
|
||||
showPopup(CreateIssue, { space, shouldSaveDraft: true }, 'top', () => (closed = true))
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiNav-subheader">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
label={draftExists ? tracker.string.ResumeDraft : tracker.string.NewIssue}
|
||||
label={draftExists || !closed ? tracker.string.ResumeDraft : tracker.string.NewIssue}
|
||||
justify={'left'}
|
||||
kind={'primary'}
|
||||
width={'100%'}
|
||||
|
@ -27,6 +27,7 @@
|
||||
import DraftIssueChildEditor from './templates/DraftIssueChildEditor.svelte'
|
||||
import DraftIssueChildList from './templates/DraftIssueChildList.svelte'
|
||||
|
||||
export let parendIssueId: Ref<Issue>
|
||||
export let projectId: Ref<Project>
|
||||
export let project: Project | undefined
|
||||
export let sprint: Ref<Sprint> | null = null
|
||||
@ -36,7 +37,7 @@
|
||||
let lastProject = project
|
||||
|
||||
let isCollapsed = false
|
||||
$: isCreatingMode = $draftsStore[tracker.ids.IssueDraftChild] !== undefined
|
||||
$: isCreatingMode = $draftsStore[`${parendIssueId}_subIssue`] !== undefined
|
||||
let isManualCreating = false
|
||||
$: isCreating = isCreatingMode || isManualCreating
|
||||
|
||||
@ -233,6 +234,7 @@
|
||||
<ExpandCollapse isExpanded={!isCollapsed} on:changeContent>
|
||||
<DraftIssueChildEditor
|
||||
bind:this={draftChild}
|
||||
{parendIssueId}
|
||||
{project}
|
||||
{component}
|
||||
{sprint}
|
||||
|
@ -83,15 +83,15 @@
|
||||
value: ValueType,
|
||||
defaultStatus: Ref<IssueStatus> | undefined
|
||||
): WithLookup<IssueStatus> | undefined {
|
||||
if (defaultStatus !== undefined) {
|
||||
defaultIssueStatus = undefined
|
||||
changeStatus(defaultStatus, false)
|
||||
return statuses?.find((status) => status._id === defaultStatus)
|
||||
if (value.status !== undefined) {
|
||||
const current = statuses?.find((status) => status._id === value.status)
|
||||
if (current) return current
|
||||
}
|
||||
if (defaultIssueStatus !== undefined) {
|
||||
const res = statuses?.find((status) => status._id === defaultStatus)
|
||||
changeStatus(res?._id, false)
|
||||
return res
|
||||
}
|
||||
const current = statuses?.find((status) => status._id === value.status)
|
||||
if (current) return current
|
||||
changeStatus(statuses?.[0]?._id, false)
|
||||
return statuses?.[0]
|
||||
}
|
||||
|
||||
$: selectedStatus = getSelectedStatus(statuses, value, defaultIssueStatus)
|
||||
|
@ -19,25 +19,26 @@
|
||||
import presentation, { DraftController, getClient, KeyedAttribute } from '@hcengineering/presentation'
|
||||
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
||||
import { calcRank, Issue, IssueDraft, IssuePriority, Project } from '@hcengineering/tracker'
|
||||
import { addNotification, Button, Component, EditBox, deviceOptionsStore, ButtonSize } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { addNotification, Button, ButtonSize, Component, deviceOptionsStore, EditBox } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { generateIssueShortLink, getIssueId } from '../../../issues'
|
||||
import tracker from '../../../plugin'
|
||||
import AssigneeEditor from '../AssigneeEditor.svelte'
|
||||
import IssueNotification from '../IssueNotification.svelte'
|
||||
import PriorityEditor from '../PriorityEditor.svelte'
|
||||
import StatusEditor from '../StatusEditor.svelte'
|
||||
import EstimationEditor from '../timereport/EstimationEditor.svelte'
|
||||
import { generateIssueShortLink, getIssueId } from '../../../issues'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
export let parentIssue: Issue
|
||||
export let currentProject: Project
|
||||
export let shouldSaveDraft: boolean = false
|
||||
|
||||
const draftController = new DraftController<IssueDraft>(parentIssue._id)
|
||||
const draftController = new DraftController<IssueDraft>(`${parentIssue._id}_subIssue`)
|
||||
const draft = shouldSaveDraft ? draftController.get() : undefined
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
onDestroy(() => draftController.unsubscribe())
|
||||
onDestroy(() => draftController.destroy())
|
||||
|
||||
let object = draft ?? getIssueDefaults()
|
||||
|
||||
@ -77,10 +78,14 @@
|
||||
sprint: parentIssue.sprint
|
||||
}
|
||||
|
||||
if (shouldSaveDraft) {
|
||||
draftController.watch(object, empty)
|
||||
function objectChange (object: IssueDraft, empty: any) {
|
||||
if (shouldSaveDraft) {
|
||||
draftController.save(object, empty)
|
||||
}
|
||||
}
|
||||
|
||||
$: objectChange(object, empty)
|
||||
|
||||
function resetToDefaults () {
|
||||
object = getIssueDefaults()
|
||||
focusIssueTitle?.()
|
||||
|
@ -46,7 +46,7 @@
|
||||
|
||||
let subIssueEditorRef: HTMLDivElement
|
||||
let isCollapsed = false
|
||||
let isCreating = $draftsStore[issue._id] !== undefined
|
||||
let isCreating = $draftsStore[`${issue._id}_subIssue`] !== undefined
|
||||
|
||||
$: hasSubIssues = issue.subIssues > 0
|
||||
|
||||
|
@ -15,9 +15,9 @@
|
||||
<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 presentation, { DraftController, getClient, KeyedAttribute } from '@hcengineering/presentation'
|
||||
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
||||
import { Component as ComponentType, IssueDraft, IssuePriority, Project, Sprint } from '@hcengineering/tracker'
|
||||
import { Component as ComponentType, Issue, IssueDraft, IssuePriority, Project, Sprint } from '@hcengineering/tracker'
|
||||
import { Button, Component, EditBox } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
@ -25,7 +25,9 @@
|
||||
import PriorityEditor from '../issues/PriorityEditor.svelte'
|
||||
import StatusEditor from '../issues/StatusEditor.svelte'
|
||||
import EstimationEditor from './EstimationEditor.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
export let parendIssueId: Ref<Issue>
|
||||
export let project: Project
|
||||
export let sprint: Ref<Sprint> | null = null
|
||||
export let component: Ref<ComponentType> | null = null
|
||||
@ -35,12 +37,14 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const draftController = new DraftController<IssueDraft>(tracker.ids.IssueDraftChild)
|
||||
const draft = shouldSaveDraft ? ($draftsStore[tracker.ids.IssueDraftChild] as IssueDraft) : undefined
|
||||
const draftController = new DraftController<IssueDraft>(`${parendIssueId}_subIssue`)
|
||||
const draft = shouldSaveDraft ? draftController.get() : undefined
|
||||
let object = childIssue !== undefined ? childIssue : draft ?? getIssueDefaults()
|
||||
let thisRef: HTMLDivElement
|
||||
let focusIssueTitle: () => void
|
||||
|
||||
onDestroy(() => draftController.destroy())
|
||||
|
||||
const key: KeyedAttribute = {
|
||||
key: 'labels',
|
||||
attr: client.getHierarchy().getAttribute(tracker.class.IssueTemplate, 'labels')
|
||||
@ -48,16 +52,6 @@
|
||||
|
||||
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(),
|
||||
|
Loading…
Reference in New Issue
Block a user