diff --git a/packages/kanban/src/utils.ts b/packages/kanban/src/utils.ts index ae91f408bb..0961261f6e 100644 --- a/packages/kanban/src/utils.ts +++ b/packages/kanban/src/utils.ts @@ -40,5 +40,8 @@ export const calcRank = (prev?: { rank: string }, next?: { rank: string }): stri const a = prev?.rank !== undefined ? LexoRank.parse(prev.rank) : LexoRank.min() const b = next?.rank !== undefined ? LexoRank.parse(next.rank) : LexoRank.max() + if (a.equals(b)) { + return a.genNext().toString() + } return a.between(b).toString() } diff --git a/packages/presentation/src/utils.ts b/packages/presentation/src/utils.ts index 35381225bf..e7dbec7130 100644 --- a/packages/presentation/src/utils.ts +++ b/packages/presentation/src/utils.ts @@ -104,6 +104,10 @@ export function getClient (): TxOperations { * @public */ export function setClient (_client: Client): void { + if (liveQuery !== undefined) { + void liveQuery.close() + } + const needRefresh = liveQuery !== undefined liveQuery = new LQ(_client) client = new UIClient(_client, liveQuery) _client.notify = (tx: Tx) => { @@ -111,17 +115,23 @@ export function setClient (_client: Client): void { txListeners.forEach((it) => it(tx)) } + if (needRefresh) { + refreshClient() + } } /** * @public */ export function refreshClient (): void { - if (liveQuery !== undefined) { - void liveQuery.refreshConnect() + void liveQuery?.refreshConnect() + for (const q of globalQueries) { + q.refreshClient() } } +const globalQueries: LiveQuery[] = [] + /** * @public */ @@ -137,6 +147,8 @@ export class LiveQuery { onDestroy(() => { this.unsubscribe() }) + } else { + globalQueries.push(this) } } @@ -149,11 +161,21 @@ export class LiveQuery { if (!this.needUpdate(_class, query, callback, options)) { return false } + return this.doQuery(_class, query, callback, options) + } + + private doQuery( + _class: Ref>, + query: DocumentQuery, + callback: (result: FindResult) => void, + options: FindOptions | undefined + ): boolean { this.unsubscribe() this.oldCallback = callback this.oldClass = _class this.oldOptions = options this.oldQuery = query + const unsub = liveQuery.query(_class, query, callback, options) this.unsubscribe = () => { unsub() @@ -166,6 +188,16 @@ export class LiveQuery { return true } + refreshClient (): void { + if (this.oldClass !== undefined && this.oldQuery !== undefined && this.oldCallback !== undefined) { + const _class = this.oldClass + const query = this.oldQuery + const callback = this.oldCallback + const options = this.oldOptions + this.doQuery(_class, query, callback, options) + } + } + private needUpdate( _class: Ref>, query: DocumentQuery, diff --git a/packages/query/src/index.ts b/packages/query/src/index.ts index 3e927fa418..9afe9c002b 100644 --- a/packages/query/src/index.ts +++ b/packages/query/src/index.ts @@ -64,7 +64,7 @@ interface Query { * @public */ export class LiveQuery extends TxProcessor implements Client { - private readonly client: Client + private client: Client private readonly queries: Map>, Query[]> = new Map>, Query[]>() private readonly queue: Query[] = [] @@ -73,6 +73,11 @@ export class LiveQuery extends TxProcessor implements Client { this.client = client } + async updateClient (client: Client): Promise { + this.client = client + await this.refreshConnect() + } + async close (): Promise { return await this.client.close() } @@ -89,7 +94,20 @@ export class LiveQuery extends TxProcessor implements Client { async refreshConnect (): Promise { for (const q of [...this.queue]) { if (!(await this.removeFromQueue(q))) { - await this.refresh(q) + try { + await this.refresh(q) + } catch (err) { + console.error(err) + } + } + } + for (const v of this.queries.values()) { + for (const q of v) { + try { + await this.refresh(q) + } catch (err) { + console.error(err) + } } } } diff --git a/plugins/client-resources/src/connection.ts b/plugins/client-resources/src/connection.ts index 279ced9efd..ddd11fa0d2 100644 --- a/plugins/client-resources/src/connection.ts +++ b/plugins/client-resources/src/connection.ts @@ -50,7 +50,7 @@ class RequestPromise { resolve!: (value?: any) => void reject!: (reason?: any) => void reconnect?: () => void - constructor () { + constructor (readonly method: string, readonly params: any[]) { this.promise = new Promise((resolve, reject) => { this.resolve = resolve this.reject = reject @@ -64,6 +64,7 @@ class Connection implements ClientConnection { private lastId = 0 private readonly interval: number private readonly sessionId = generateId() as string + private closed = false constructor ( private readonly url: string, @@ -80,6 +81,7 @@ class Connection implements ClientConnection { } async close (): Promise { + this.closed = true clearInterval(this.interval) if (this.websocket !== null) { if (this.websocket instanceof Promise) { @@ -158,7 +160,7 @@ class Connection implements ClientConnection { } this.requests.delete(resp.id) if (resp.error !== undefined) { - console.log('ERROR', resp.id) + console.log('ERROR', promise, resp.id) promise.reject(new PlatformError(resp.error)) } else { promise.resolve(resp.result) @@ -212,8 +214,11 @@ class Connection implements ClientConnection { // If not defined, on reconnect with timeout, will retry automatically. retry?: () => Promise }): Promise { + if (this.closed) { + throw new PlatformError(unknownError('connection closed')) + } const id = this.lastId++ - const promise = new RequestPromise() + const promise = new RequestPromise(data.method, data.params) const sendData = async (): Promise => { if (this.websocket instanceof Promise) { diff --git a/plugins/contact-resources/src/utils.ts b/plugins/contact-resources/src/utils.ts index dcc0bae994..5e9dab32e6 100644 --- a/plugins/contact-resources/src/utils.ts +++ b/plugins/contact-resources/src/utils.ts @@ -34,10 +34,8 @@ import { FilterQuery } from '@hcengineering/view-resources' import { get, writable } from 'svelte/store' import contact from './plugin' -const client = getClient() - export async function getChannelProviders (): Promise, ChannelProvider>> { - const cp = await client.findAll(contact.class.ChannelProvider, {}) + const cp = await getClient().findAll(contact.class.ChannelProvider, {}) const map = new Map, ChannelProvider>() for (const provider of cp) { map.set(provider._id, provider) diff --git a/plugins/task/src/utils.ts b/plugins/task/src/utils.ts index ae91f408bb..f8b573e900 100644 --- a/plugins/task/src/utils.ts +++ b/plugins/task/src/utils.ts @@ -39,6 +39,8 @@ export const genRanks = (count: number): Generator => export const calcRank = (prev?: { rank: string }, next?: { rank: string }): string => { const a = prev?.rank !== undefined ? LexoRank.parse(prev.rank) : LexoRank.min() const b = next?.rank !== undefined ? LexoRank.parse(next.rank) : LexoRank.max() - + if (a.equals(b)) { + return a.genNext().toString() + } return a.between(b).toString() } diff --git a/plugins/tracker-resources/src/components/icons/StatusIcon.svelte b/plugins/tracker-resources/src/components/icons/StatusIcon.svelte index eec51029c5..7a05b9fdcd 100644 --- a/plugins/tracker-resources/src/components/icons/StatusIcon.svelte +++ b/plugins/tracker-resources/src/components/icons/StatusIcon.svelte @@ -15,6 +15,7 @@
diff --git a/plugins/tracker-resources/src/components/issues/IssueStatusIcon.svelte b/plugins/tracker-resources/src/components/issues/IssueStatusIcon.svelte index 62f858b444..2ce96483e2 100644 --- a/plugins/tracker-resources/src/components/issues/IssueStatusIcon.svelte +++ b/plugins/tracker-resources/src/components/issues/IssueStatusIcon.svelte @@ -40,7 +40,7 @@ $: if (value.category === tracker.issueStatusCategory.Started) { const _s = [ - ...$statusStore.filter( + ...$statusStore.statuses.filter( (it) => it.attachedTo === value.attachedTo && it.category === tracker.issueStatusCategory.Started ) ] diff --git a/plugins/tracker-resources/src/components/issues/StatusEditor.svelte b/plugins/tracker-resources/src/components/issues/StatusEditor.svelte index 2da4231146..18be26864d 100644 --- a/plugins/tracker-resources/src/components/issues/StatusEditor.svelte +++ b/plugins/tracker-resources/src/components/issues/StatusEditor.svelte @@ -65,7 +65,7 @@ ) } - $: statuses = $statusStore.filter((it) => it.attachedTo === value?.space) + $: statuses = $statusStore.statuses.filter((it) => it.attachedTo === value?.space) $: selectedStatus = statuses?.find((status) => status._id === value.status) ?? statuses?.[0] $: selectedStatusLabel = shouldShowLabel ? selectedStatus?.name : undefined diff --git a/plugins/tracker-resources/src/components/issues/StatusPresenter.svelte b/plugins/tracker-resources/src/components/issues/StatusPresenter.svelte index 369ccf9c36..846159bfca 100644 --- a/plugins/tracker-resources/src/components/issues/StatusPresenter.svelte +++ b/plugins/tracker-resources/src/components/issues/StatusPresenter.svelte @@ -14,7 +14,7 @@ --> {#if value} - {@const icon = $statusByIdStore.get(value._id)?.$lookup?.category?.icon} + {@const icon = $statusStore.byId.get(value._id)?.$lookup?.category?.icon}
{#if icon} diff --git a/plugins/tracker-resources/src/components/issues/StatusRefPresenter.svelte b/plugins/tracker-resources/src/components/issues/StatusRefPresenter.svelte index a23248560a..2f98d49d8f 100644 --- a/plugins/tracker-resources/src/components/issues/StatusRefPresenter.svelte +++ b/plugins/tracker-resources/src/components/issues/StatusRefPresenter.svelte @@ -15,7 +15,7 @@ {#if value} - + {/if} diff --git a/plugins/tracker-resources/src/components/issues/TitlePresenter.svelte b/plugins/tracker-resources/src/components/issues/TitlePresenter.svelte index be1a409f5b..5bfaec4fb8 100644 --- a/plugins/tracker-resources/src/components/issues/TitlePresenter.svelte +++ b/plugins/tracker-resources/src/components/issues/TitlePresenter.svelte @@ -26,16 +26,16 @@ {#if value} - - + + {value.title} - - + + {#if showParent} {/if} diff --git a/plugins/tracker-resources/src/components/issues/edit/SubIssuesSelector.svelte b/plugins/tracker-resources/src/components/issues/edit/SubIssuesSelector.svelte index 64f1b651e8..c04d377dee 100644 --- a/plugins/tracker-resources/src/components/issues/edit/SubIssuesSelector.svelte +++ b/plugins/tracker-resources/src/components/issues/edit/SubIssuesSelector.svelte @@ -29,7 +29,7 @@ } from '@hcengineering/ui' import { getIssueId } from '../../../issues' import tracker from '../../../plugin' - import { statusByIdStore, statusStore, subIssueListProvider } from '../../../utils' + import { statusStore, subIssueListProvider } from '../../../utils' export let value: WithLookup export let currentProject: Project | undefined = undefined @@ -77,7 +77,7 @@ } $: if (subIssues) { - const doneStatuses = $statusStore + const doneStatuses = $statusStore.statuses .filter((s) => s.category === tracker.issueStatusCategory.Completed) .map((p) => p._id) countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length @@ -115,7 +115,7 @@ id: iss._id, text, isSelected: iss._id === value._id, - ...getIssueStatusIcon(iss, $statusByIdStore) + ...getIssueStatusIcon(iss, $statusStore.byId) } }), width: 'large' diff --git a/plugins/tracker-resources/src/components/issues/related/RelatedIssueSelector.svelte b/plugins/tracker-resources/src/components/issues/related/RelatedIssueSelector.svelte index ce049b01c1..d8d2e48f7e 100644 --- a/plugins/tracker-resources/src/components/issues/related/RelatedIssueSelector.svelte +++ b/plugins/tracker-resources/src/components/issues/related/RelatedIssueSelector.svelte @@ -29,7 +29,7 @@ } from '@hcengineering/ui' import { getIssueId } from '../../../issues' import tracker from '../../../plugin' - import { statusByIdStore, statusStore, subIssueListProvider } from '../../../utils' + import { statusStore, subIssueListProvider } from '../../../utils' export let object: WithLookup | undefined export let value: WithLookup | undefined @@ -69,7 +69,7 @@ } $: if (subIssues) { - const doneStatuses = $statusStore + const doneStatuses = $statusStore.statuses .filter((s) => s.category === tracker.issueStatusCategory.Completed) .map((p) => p._id) countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length @@ -101,7 +101,7 @@ value: subIssues.map((iss) => { const text = currentProject ? `${getIssueId(currentProject, iss)} ${iss.title}` : iss.title - return { id: iss._id, text, isSelected: false, ...getIssueStatusIcon(iss, $statusByIdStore) } + return { id: iss._id, text, isSelected: false, ...getIssueStatusIcon(iss, $statusStore.byId) } }), width: 'large' }, diff --git a/plugins/tracker-resources/src/components/sprints/IssueStatistics.svelte b/plugins/tracker-resources/src/components/sprints/IssueStatistics.svelte index 24fdd1b168..fb141de7fc 100644 --- a/plugins/tracker-resources/src/components/sprints/IssueStatistics.svelte +++ b/plugins/tracker-resources/src/components/sprints/IssueStatistics.svelte @@ -17,7 +17,7 @@ import { Issue } from '@hcengineering/tracker' import { floorFractionDigits, Label } from '@hcengineering/ui' import tracker from '../../plugin' - import { statusByIdStore } from '../../utils' + import { statusStore } from '../../utils' import EstimationProgressCircle from '../issues/timereport/EstimationProgressCircle.svelte' import TimePresenter from '../issues/timereport/TimePresenter.svelte' export let docs: Issue[] | undefined = undefined @@ -28,13 +28,13 @@ $: noParents = docs?.filter((it) => !ids.has(it.attachedTo as Ref)) $: rootNoBacklogIssues = noParents?.filter( - (it) => $statusByIdStore.get(it.status)?.category !== tracker.issueStatusCategory.Backlog + (it) => $statusStore.byId.get(it.status)?.category !== tracker.issueStatusCategory.Backlog ) $: totalEstimation = floorFractionDigits( (rootNoBacklogIssues ?? [{ estimation: 0, childInfo: [] } as unknown as Issue]) .map((it) => { - const cat = $statusByIdStore.get(it.status)?.category + const cat = $statusStore.byId.get(it.status)?.category let retEst = it.estimation if (it.childInfo?.length > 0) { diff --git a/plugins/tracker-resources/src/components/workflow/Statuses.svelte b/plugins/tracker-resources/src/components/workflow/Statuses.svelte index 1562b79812..8a06529888 100644 --- a/plugins/tracker-resources/src/components/workflow/Statuses.svelte +++ b/plugins/tracker-resources/src/components/workflow/Statuses.svelte @@ -66,10 +66,10 @@ } async function addStatus () { - if (editingStatus?.name && editingStatus?.category && $statusStore) { - const categoryStatuses = $statusStore.filter((s) => s.category === editingStatus!.category) + if (editingStatus?.name && editingStatus?.category) { + const categoryStatuses = $statusStore.statuses.filter((s) => s.category === editingStatus!.category) const prevStatus = categoryStatuses[categoryStatuses.length - 1] - const nextStatus = $statusStore[$statusStore.findIndex(({ _id }) => _id === prevStatus._id) + 1] + const nextStatus = $statusStore.statuses[$statusStore.statuses.findIndex(({ _id }) => _id === prevStatus._id) + 1] isSaving = true await client.addCollection( @@ -93,9 +93,9 @@ } async function editStatus () { - if ($statusStore && statusCategories && editingStatus?.name && editingStatus?.category && '_id' in editingStatus) { + if (statusCategories && editingStatus?.name && editingStatus?.category && '_id' in editingStatus) { const statusId = '_id' in editingStatus ? editingStatus._id : undefined - const status = statusId && $statusStore.find(({ _id }) => _id === statusId) + const status = statusId && $statusStore.byId.get(statusId) if (!status) { return @@ -157,12 +157,14 @@ }, undefined, async (result) => { - if (result && project && $statusStore) { + if (result && project) { isSaving = true await client.removeDoc(status._class, status.space, status._id) if (project.defaultIssueStatus === status._id) { - const newDefaultStatus = $statusStore.find((s) => s._id !== status._id && s.category === status.category) + const newDefaultStatus = $statusStore.statuses.find( + (s) => s._id !== status._id && s.category === status.category && s.space === status.space + ) if (newDefaultStatus?._id) { await updateProjectDefaultStatus(newDefaultStatus._id) } @@ -196,12 +198,12 @@ } async function handleDrop (toItem: IssueStatus) { - if ($statusStore && draggingStatus?._id !== toItem._id && draggingStatus?.category === toItem.category) { + if (draggingStatus?._id !== toItem._id && draggingStatus?.category === toItem.category) { const fromIndex = getStatusIndex(draggingStatus) const toIndex = getStatusIndex(toItem) const [prev, next] = [ - $statusStore[fromIndex < toIndex ? toIndex : toIndex - 1], - $statusStore[fromIndex < toIndex ? toIndex + 1 : toIndex] + $statusStore.statuses[fromIndex < toIndex ? toIndex : toIndex - 1], + $statusStore.statuses[fromIndex < toIndex ? toIndex + 1 : toIndex] ] isSaving = true @@ -213,7 +215,7 @@ } function getStatusIndex (status: IssueStatus) { - return $statusStore?.findIndex(({ _id }) => _id === status._id) ?? -1 + return $statusStore.statuses.findIndex(({ _id }) => _id === status._id) ?? -1 } function resetDrag () { @@ -242,14 +244,14 @@
- {#if project === undefined || statusCategories === undefined || $statusStore === undefined} + {#if project === undefined || statusCategories === undefined || $statusStore.statuses.length === 0} {:else}
{#each statusCategories as category} {@const statuses = - $statusStore?.filter((s) => s.attachedTo === projectId && s.category === category._id) ?? []} + $statusStore.statuses.filter((s) => s.attachedTo === projectId && s.category === category._id) ?? []} {@const isSingle = statuses.length === 1}