platform/plugins/view-resources/src/selection.ts
Denis Bunakalya 49bf38cf0a
Merge branch 'main' of github.com:hcengineering/anticrm into show-filter-items-current-space
Signed-off-by: Denis Bunakalya <denis.bunakalya@xored.com>
2023-01-18 11:53:37 +04:00

180 lines
4.7 KiB
TypeScript

import { Doc } from '@hcengineering/core'
import { panelstore } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import { Unsubscriber, writable } from 'svelte/store'
/**
* @public
*
*/
export type SelectDirection = 'vertical' | 'horizontal'
export interface SelectionFocusProvider {
// -1 - previous
// 0 - selec of as current
// 1 - next
// * If vertical, next will return item under.
// * If horizontal, next will return item on right.
// of - document offset from we requesting.
select?: (offset: 1 | -1 | 0, of?: Doc, direction?: SelectDirection) => void
// Update documents content
update: (docs: Doc[]) => void
// Return selection index from list of documents.
current: (doc?: FocusSelection) => number | undefined
// Update focused element, selection is not changed.
updateFocus: (doc: Doc) => void
// Update curent selection list, focus items it not updated.
updateSelection: (docs: Doc[], value: boolean) => void
// Return all selectable documents
docs: () => Doc[]
}
/**
* @public
*
* Define document selection inside platform.
*/
export interface FocusSelection {
// Focused document
focus?: Doc
// Additional interface to select, next/prev etc.
provider?: SelectionFocusProvider
}
/**
* @public
*/
export const focusStore = writable<FocusSelection>({})
export const selectionStore = writable<Doc[]>([])
export const previewDocument = writable<Doc | undefined>()
panelstore.subscribe((val) => {
previewDocument.set(undefined)
})
/**
* @public
*/
export function updateFocus (selection?: FocusSelection): void {
focusStore.update((cur) => {
const now = Date.now()
if (selection === undefined || now - ((cur as any).now ?? 0) >= 50) {
cur.focus = selection?.focus
cur.provider = selection?.provider
;(cur as any).now = now
previewDocument.update((old) => {
if (old !== undefined) {
return selection?.focus
}
})
}
return cur
})
// We need to clear selection items not belong to passed provider.
if (selection?.provider !== undefined) {
const docs = new Set(selection?.provider.docs().map((it) => it._id))
selectionStore.update((old) => {
return old.filter((it) => docs.has(it._id))
})
}
}
const providers: ListSelectionProvider[] = []
/**
* @public
*
* List selection provider
*/
export class ListSelectionProvider implements SelectionFocusProvider {
private _docs: Doc[] = []
_current?: FocusSelection
private readonly unsubscribe: Unsubscriber
constructor (
private readonly delegate: (offset: 1 | -1 | 0, of?: Doc, direction?: SelectDirection) => void,
autoDestroy = true
) {
this.unsubscribe = focusStore.subscribe((doc) => {
this._current = doc
})
providers.push(this)
if (autoDestroy) {
onDestroy(() => {
this.destroy()
})
}
}
static Pop (): void {
if (providers.length === 0) return
const last = providers[providers.length - 1]
last.destroy()
}
destroy (): void {
const thisIndex = providers.findIndex((p) => p === this)
providers.splice(thisIndex, 1)
if (thisIndex === providers.length) {
if (providers.length > 0) {
const current = providers[providers.length - 1]
const index = current.current()
const target = index !== undefined ? current.docs()[index] : undefined
updateFocus({ focus: target, provider: current })
} else {
updateFocus()
}
}
this.unsubscribe()
}
select (offset: 1 | -1 | 0, of?: Doc, direction?: SelectDirection): void {
this.delegate(offset, of, direction)
}
update (docs: Doc[]): void {
this._docs = docs
selectionStore.update((docs) => {
const ids = new Set(docs.map((it) => it._id))
return this._docs.filter((it) => ids.has(it._id))
})
if (this._docs.length > 0) {
if (this._current?.focus === undefined) {
this.delegate(0, undefined, 'vertical')
} else {
// Check if we don't have object, we need to select first one.
this.delegate(0, this._current?.focus, 'vertical')
}
updateFocus({ focus: this._current?.focus, provider: this })
}
}
docs (): Doc[] {
return this._docs
}
updateFocus (doc: Doc): void {
updateFocus({ focus: doc, provider: this })
}
updateSelection (docs: Doc[], value: boolean): void {
selectionStore.update((selection) => {
const docsSet = new Set(docs.map((it) => it._id))
const noDocs = selection.filter((it) => !docsSet.has(it._id))
return value ? [...noDocs, ...docs] : noDocs
})
}
current (doc?: FocusSelection): number | undefined {
const index = this._docs.findIndex((it) => it._id === (doc?.focus?._id ?? this._current?.focus?._id))
return index === -1 ? undefined : index
}
}