mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
UBERF-5564: rework groupping and support PersonAccount (#5525)
Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
parent
4496530fed
commit
7f2a8779c6
@ -507,6 +507,16 @@ export function createModel (builder: Builder): void {
|
||||
pinned: true
|
||||
})
|
||||
|
||||
builder.mixin(core.class.Account, core.class.Class, view.mixin.Aggregation, {
|
||||
createAggregationManager: contact.aggregation.CreatePersonAggregationManager,
|
||||
setStoreFunc: contact.function.SetPersonStore,
|
||||
filterFunc: contact.function.PersonFilterFunction
|
||||
})
|
||||
|
||||
builder.mixin(core.class.Account, core.class.Class, view.mixin.Groupping, {
|
||||
grouppingManager: contact.aggregation.GrouppingPersonManager
|
||||
})
|
||||
|
||||
builder.mixin(contact.mixin.Employee, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: contact.component.EditEmployee,
|
||||
pinned: true
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import { contactId } from '@hcengineering/contact'
|
||||
import contact from '@hcengineering/contact-resources/src/plugin'
|
||||
import type { Client, Doc, Ref } from '@hcengineering/core'
|
||||
import type { Client, Doc, DocManager, Ref } from '@hcengineering/core'
|
||||
import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineering/model-presentation'
|
||||
import { type NotificationGroup } from '@hcengineering/notification'
|
||||
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
|
||||
@ -139,6 +139,8 @@ export default mergeIds(contactId, contact, {
|
||||
GetContactLastName: '' as Resource<TemplateFieldFunc>,
|
||||
ContactTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>,
|
||||
ChannelTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>,
|
||||
ChannelIdentifierProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>
|
||||
ChannelIdentifierProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>,
|
||||
SetPersonStore: '' as Resource<(manager: DocManager<any>) => void>,
|
||||
PersonFilterFunction: '' as Resource<(doc: Doc, target: Doc) => boolean>
|
||||
}
|
||||
})
|
||||
|
@ -166,7 +166,7 @@ export class TSpacesTypeData extends TSpace implements RolesAssignment {
|
||||
}
|
||||
|
||||
@Model(core.class.Account, core.class.Doc, DOMAIN_MODEL)
|
||||
@UX(core.string.Account)
|
||||
@UX(core.string.Account, undefined, undefined, 'name')
|
||||
export class TAccount extends TDoc implements Account {
|
||||
email!: string
|
||||
role!: AccountRole
|
||||
|
@ -94,7 +94,13 @@ function defineSortAndGrouping (builder: Builder): void {
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.Aggregation, {
|
||||
createAggregationManager: tracker.aggregation.CreateComponentAggregationManager
|
||||
createAggregationManager: tracker.aggregation.CreateComponentAggregationManager,
|
||||
setStoreFunc: tracker.function.SetComponentStore,
|
||||
filterFunc: tracker.function.ComponentFilterFunction
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.Groupping, {
|
||||
grouppingManager: tracker.aggregation.GrouppingComponentManager
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.AllValuesFunc, {
|
||||
|
@ -15,7 +15,7 @@
|
||||
//
|
||||
import { type DocUpdateMessageViewlet } from '@hcengineering/activity'
|
||||
import { type ChatMessageViewlet } from '@hcengineering/chunter'
|
||||
import { type StatusCategory, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type StatusCategory, type Doc, type Ref, type DocManager } from '@hcengineering/core'
|
||||
import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineering/model-presentation'
|
||||
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
import { mergeIds, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
@ -119,5 +119,10 @@ export default mergeIds(trackerId, tracker, {
|
||||
Started: '' as Ref<StatusCategory>,
|
||||
Completed: '' as Ref<StatusCategory>,
|
||||
Canceled: '' as Ref<StatusCategory>
|
||||
},
|
||||
|
||||
function: {
|
||||
SetComponentStore: '' as Resource<(manager: DocManager<any>) => void>,
|
||||
ComponentFilterFunction: '' as Resource<(doc: Doc, target: Doc) => boolean>
|
||||
}
|
||||
})
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
DOMAIN_MODEL,
|
||||
type Data,
|
||||
type Doc,
|
||||
type DocManager,
|
||||
type DocumentQuery,
|
||||
type Domain,
|
||||
type Ref,
|
||||
@ -274,6 +275,8 @@ export class TGroupping extends TClass implements Groupping {
|
||||
@Mixin(view.mixin.Aggregation, core.class.Class)
|
||||
export class TAggregation extends TClass implements Aggregation {
|
||||
createAggregationManager!: CreateAggregationManagerFunc
|
||||
setStoreFunc!: Resource<(manager: DocManager<any>) => void>
|
||||
filterFunc!: Resource<(doc: Doc, target: Doc) => boolean>
|
||||
}
|
||||
|
||||
@Mixin(view.mixin.ObjectIcon, core.class.Class)
|
||||
|
@ -315,29 +315,36 @@ export class AggregateValue {
|
||||
*/
|
||||
export type CategoryType = number | string | undefined | Ref<Doc> | AggregateValue
|
||||
|
||||
export interface IDocManager<T extends Doc> {
|
||||
get: (ref: Ref<T>) => T | undefined
|
||||
getDocs: () => T[]
|
||||
getIdMap: () => IdMap<T>
|
||||
filter: (predicate: (value: T) => boolean) => T[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class DocManager {
|
||||
protected readonly byId: IdMap<Doc>
|
||||
export class DocManager<T extends Doc> implements IDocManager<T> {
|
||||
protected readonly byId: IdMap<T>
|
||||
|
||||
constructor (protected readonly docs: Doc[]) {
|
||||
constructor (protected readonly docs: T[]) {
|
||||
this.byId = toIdMap(docs)
|
||||
}
|
||||
|
||||
get (ref: Ref<Doc>): Doc | undefined {
|
||||
get (ref: Ref<T>): T | undefined {
|
||||
return this.byId.get(ref)
|
||||
}
|
||||
|
||||
getDocs (): Doc[] {
|
||||
getDocs (): T[] {
|
||||
return this.docs
|
||||
}
|
||||
|
||||
getIdMap (): IdMap<Doc> {
|
||||
getIdMap (): IdMap<T> {
|
||||
return this.byId
|
||||
}
|
||||
|
||||
filter (predicate: (value: Doc) => boolean): Doc[] {
|
||||
filter (predicate: (value: T) => boolean): T[] {
|
||||
return this.docs.filter(predicate)
|
||||
}
|
||||
}
|
||||
|
@ -74,8 +74,15 @@
|
||||
let limitedObjects: IdMap<DocWithRank> = new Map()
|
||||
|
||||
const docQuery = createQuery()
|
||||
|
||||
$: groupQuery = { ...query, [groupByKey]: typeof state === 'object' ? { $in: state.values } : state }
|
||||
$: groupQuery = {
|
||||
...query,
|
||||
[groupByKey]:
|
||||
typeof state === 'object'
|
||||
? state.name !== undefined
|
||||
? { $in: state.values.flatMap((x) => x._id) }
|
||||
: undefined
|
||||
: state
|
||||
}
|
||||
|
||||
$: void limiter.add(async () => {
|
||||
docQuery.query(
|
||||
|
@ -15,12 +15,13 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { AggregateValue, Ref } from '@hcengineering/core'
|
||||
import { IconSize } from '@hcengineering/ui'
|
||||
import { personAccountByIdStore } from '../utils'
|
||||
import PersonAccountPresenter from './PersonAccountPresenter.svelte'
|
||||
import { personStore } from '..'
|
||||
|
||||
export let value: Ref<PersonAccount>
|
||||
export let value: Ref<PersonAccount> | AggregateValue
|
||||
export let avatarSize: IconSize = 'x-small'
|
||||
export let shouldShowAvatar: boolean = true
|
||||
export let shouldShowName: boolean = true
|
||||
@ -30,7 +31,8 @@
|
||||
export let noUnderline: boolean = false
|
||||
export let compact = false
|
||||
|
||||
$: account = $personAccountByIdStore.get(value)
|
||||
$: _value = $personStore.get(typeof value === 'string' ? value : (value?.values?.[0]?._id as Ref<PersonAccount>))
|
||||
$: account = $personAccountByIdStore.get(_value?._id ?? (value as Ref<PersonAccount>))
|
||||
</script>
|
||||
|
||||
{#if account}
|
||||
|
@ -15,14 +15,16 @@
|
||||
//
|
||||
|
||||
import {
|
||||
type Channel,
|
||||
type AvatarInfo,
|
||||
type Contact,
|
||||
getGravatarUrl,
|
||||
getName,
|
||||
type AvatarInfo,
|
||||
type Channel,
|
||||
type Contact,
|
||||
type Person
|
||||
type Person,
|
||||
type PersonAccount
|
||||
} from '@hcengineering/contact'
|
||||
import {
|
||||
DocManager,
|
||||
type Class,
|
||||
type Client,
|
||||
type Data,
|
||||
@ -120,8 +122,9 @@ import NameChangedActivityMessage from './components/activity/NameChangedActivit
|
||||
import IconAddMember from './components/icons/AddMember.svelte'
|
||||
import ExpandRightDouble from './components/icons/ExpandRightDouble.svelte'
|
||||
import IconMembers from './components/icons/Members.svelte'
|
||||
import { AggregationManager } from '@hcengineering/view-resources'
|
||||
|
||||
import { get } from 'svelte/store'
|
||||
import { get, writable } from 'svelte/store'
|
||||
import contact from './plugin'
|
||||
import {
|
||||
channelIdentifierProvider,
|
||||
@ -140,6 +143,7 @@ import {
|
||||
getCurrentEmployeeName,
|
||||
getCurrentEmployeePosition,
|
||||
getPersonTooltip,
|
||||
grouppingPersonManager,
|
||||
resolveLocation
|
||||
} from './utils'
|
||||
|
||||
@ -293,6 +297,16 @@ async function openChannelURL (doc: Channel): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function filterPerson (doc: PersonAccount, target: PersonAccount): boolean {
|
||||
return doc.person === target.person && doc._id !== target._id
|
||||
}
|
||||
|
||||
export const personStore = writable<DocManager<PersonAccount>>(new DocManager([]))
|
||||
|
||||
function setStore (manager: DocManager<PersonAccount>): void {
|
||||
personStore.set(manager)
|
||||
}
|
||||
|
||||
export interface PersonLabelTooltip {
|
||||
personLabel?: IntlString
|
||||
placeholderLabel?: IntlString
|
||||
@ -431,9 +445,16 @@ export default async (): Promise<Resources> => ({
|
||||
ContactTitleProvider: contactTitleProvider,
|
||||
PersonTooltipProvider: getPersonTooltip,
|
||||
ChannelTitleProvider: channelTitleProvider,
|
||||
ChannelIdentifierProvider: channelIdentifierProvider
|
||||
ChannelIdentifierProvider: channelIdentifierProvider,
|
||||
SetPersonStore: setStore,
|
||||
PersonFilterFunction: filterPerson
|
||||
},
|
||||
resolver: {
|
||||
Location: resolveLocation
|
||||
},
|
||||
aggregation: {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
CreatePersonAggregationManager: AggregationManager.create,
|
||||
GrouppingPersonManager: grouppingPersonManager
|
||||
}
|
||||
})
|
||||
|
@ -18,7 +18,12 @@ import contact, { contactId } from '@hcengineering/contact'
|
||||
import { type Client, type Doc } from '@hcengineering/core'
|
||||
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
|
||||
import { type LabelAndProps, type Location } from '@hcengineering/ui'
|
||||
import { type FilterFunction, type SortFunc } from '@hcengineering/view'
|
||||
import {
|
||||
type CreateAggregationManagerFunc,
|
||||
type GrouppingManagerResource,
|
||||
type FilterFunction,
|
||||
type SortFunc
|
||||
} from '@hcengineering/view'
|
||||
|
||||
export default mergeIds(contactId, contact, {
|
||||
string: {
|
||||
@ -86,5 +91,9 @@ export default mergeIds(contactId, contact, {
|
||||
FilterChannelHasMessagesResult: '' as FilterFunction,
|
||||
FilterChannelHasNewMessagesResult: '' as FilterFunction,
|
||||
PersonTooltipProvider: '' as Resource<(client: Client, doc?: Doc | null) => Promise<LabelAndProps | undefined>>
|
||||
},
|
||||
aggregation: {
|
||||
CreatePersonAggregationManager: '' as CreateAggregationManagerFunc,
|
||||
GrouppingPersonManager: '' as GrouppingManagerResource
|
||||
}
|
||||
})
|
||||
|
@ -41,7 +41,13 @@ import core, {
|
||||
type Timestamp,
|
||||
type TxOperations,
|
||||
type UserStatus,
|
||||
type WithLookup
|
||||
type WithLookup,
|
||||
AggregateValue,
|
||||
type Space,
|
||||
type Hierarchy,
|
||||
type DocumentQuery,
|
||||
AggregateValueData,
|
||||
matchQuery
|
||||
} from '@hcengineering/core'
|
||||
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
||||
import { getEmbeddedLabel, getResource, translate } from '@hcengineering/platform'
|
||||
@ -55,11 +61,12 @@ import {
|
||||
type ResolvedLocation,
|
||||
type TabItem
|
||||
} from '@hcengineering/ui'
|
||||
import view, { type Filter } from '@hcengineering/view'
|
||||
import view, { type GrouppingManager, type Filter } from '@hcengineering/view'
|
||||
import { FilterQuery, accessDeniedStore } from '@hcengineering/view-resources'
|
||||
import { derived, get, writable } from 'svelte/store'
|
||||
|
||||
import contact from './plugin'
|
||||
import { personStore } from '.'
|
||||
|
||||
export function formatDate (dueDateMs: Timestamp): string {
|
||||
return new Date(dueDateMs).toLocaleString('default', {
|
||||
@ -431,3 +438,109 @@ export async function channelTitleProvider (client: Client, ref: Ref<Channel>, d
|
||||
|
||||
return channel.value
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const grouppingPersonManager: GrouppingManager = {
|
||||
groupByCategories: groupByPersonAccountCategories,
|
||||
groupValues: groupPersonAccountValues,
|
||||
groupValuesWithEmpty: groupPersonAccountValuesWithEmpty,
|
||||
hasValue: hasPersonAccountValue
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function groupByPersonAccountCategories (categories: any[]): AggregateValue[] {
|
||||
const mgr = get(personStore)
|
||||
|
||||
const existingCategories: AggregateValue[] = [new AggregateValue(undefined, [])]
|
||||
const personMap = new Map<string, AggregateValue>()
|
||||
|
||||
const usedSpaces = new Set<Ref<Space>>()
|
||||
const personAccountList: Array<WithLookup<PersonAccount>> = []
|
||||
for (const v of categories) {
|
||||
const personAccount = mgr.getIdMap().get(v)
|
||||
if (personAccount !== undefined) {
|
||||
personAccountList.push(personAccount)
|
||||
usedSpaces.add(personAccount.space)
|
||||
}
|
||||
}
|
||||
|
||||
for (const personAccount of personAccountList) {
|
||||
if (personAccount !== undefined) {
|
||||
let fst = personMap.get(personAccount.person)
|
||||
if (fst === undefined) {
|
||||
const people = mgr
|
||||
.getDocs()
|
||||
.filter(
|
||||
(it) => it.person === personAccount.person && (categories.includes(it._id) || usedSpaces.has(it.space))
|
||||
)
|
||||
.sort((a, b) => a.email.localeCompare(b.email))
|
||||
.map((it) => new AggregateValueData(it.person, it._id, it.space))
|
||||
fst = new AggregateValue(personAccount.person, people)
|
||||
personMap.set(personAccount.person, fst)
|
||||
existingCategories.push(fst)
|
||||
}
|
||||
}
|
||||
}
|
||||
return existingCategories
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function groupPersonAccountValues (val: Doc[], targets: Set<any>): Doc[] {
|
||||
const values = val
|
||||
const result: Doc[] = []
|
||||
const unique = [...new Set(val.map((c) => (c as PersonAccount).person))]
|
||||
unique.forEach((label, i) => {
|
||||
let exists = false
|
||||
values.forEach((c) => {
|
||||
if ((c as PersonAccount).person === label) {
|
||||
if (!exists) {
|
||||
result[i] = c
|
||||
exists = targets.has(c?._id)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function hasPersonAccountValue (value: Doc | undefined | null, values: any[]): boolean {
|
||||
const mgr = get(personStore)
|
||||
const personSet = new Set(mgr.filter((it) => it.person === (value as PersonAccount)?.person).map((it) => it._id))
|
||||
return values.some((it) => personSet.has(it))
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function groupPersonAccountValuesWithEmpty (
|
||||
hierarchy: Hierarchy,
|
||||
_class: Ref<Class<Doc>>,
|
||||
key: string,
|
||||
query: DocumentQuery<Doc> | undefined
|
||||
): Array<Ref<Doc>> {
|
||||
const mgr = get(personStore)
|
||||
let personAccountList = mgr.getDocs()
|
||||
if (query !== undefined) {
|
||||
const { [key]: st, space } = query
|
||||
const resQuery: DocumentQuery<Doc> = {}
|
||||
if (space !== undefined) {
|
||||
resQuery.space = space
|
||||
}
|
||||
if (st !== undefined) {
|
||||
resQuery._id = st
|
||||
}
|
||||
personAccountList = matchQuery<Doc>(personAccountList, resQuery, _class, hierarchy) as unknown as Array<
|
||||
WithLookup<PersonAccount>
|
||||
>
|
||||
}
|
||||
return personAccountList.map((it) => it._id)
|
||||
}
|
||||
|
@ -16,104 +16,21 @@
|
||||
import {
|
||||
AggregateValue,
|
||||
AggregateValueData,
|
||||
type AnyAttribute,
|
||||
type Class,
|
||||
type Client,
|
||||
type Doc,
|
||||
DocManager,
|
||||
type DocumentQuery,
|
||||
type Hierarchy,
|
||||
type Ref,
|
||||
SortingOrder,
|
||||
type Space,
|
||||
type Tx,
|
||||
type WithLookup,
|
||||
matchQuery
|
||||
} from '@hcengineering/core'
|
||||
import { LiveQuery } from '@hcengineering/query'
|
||||
import tracker, { type Component, ComponentManager } from '@hcengineering/tracker'
|
||||
import { type AggregationManager, type GrouppingManager } from '@hcengineering/view'
|
||||
import { type Component } from '@hcengineering/tracker'
|
||||
import { type GrouppingManager } from '@hcengineering/view'
|
||||
import { get, writable } from 'svelte/store'
|
||||
|
||||
export const componentStore = writable<ComponentManager>(new ComponentManager([]))
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class ComponentAggregationManager implements AggregationManager {
|
||||
docs: Doc[] | undefined
|
||||
mgr: ComponentManager | Promise<ComponentManager> | undefined
|
||||
query: (() => void) | undefined
|
||||
lq: LiveQuery
|
||||
lqCallback: () => void
|
||||
|
||||
private constructor (client: Client, lqCallback: () => void) {
|
||||
this.lq = new LiveQuery(client)
|
||||
this.lqCallback = lqCallback ?? (() => {})
|
||||
}
|
||||
|
||||
static create (client: Client, lqCallback: () => void): ComponentAggregationManager {
|
||||
return new ComponentAggregationManager(client, lqCallback)
|
||||
}
|
||||
|
||||
private async getManager (): Promise<ComponentManager> {
|
||||
if (this.mgr !== undefined) {
|
||||
if (this.mgr instanceof Promise) {
|
||||
this.mgr = await this.mgr
|
||||
}
|
||||
return this.mgr
|
||||
}
|
||||
this.mgr = new Promise<ComponentManager>((resolve) => {
|
||||
this.query = this.lq.query(
|
||||
tracker.class.Component,
|
||||
{},
|
||||
(res) => {
|
||||
const first = this.docs === undefined
|
||||
this.docs = res
|
||||
this.mgr = new ComponentManager(res)
|
||||
componentStore.set(this.mgr)
|
||||
if (!first) {
|
||||
this.lqCallback()
|
||||
}
|
||||
resolve(this.mgr)
|
||||
},
|
||||
{
|
||||
sort: {
|
||||
label: SortingOrder.Ascending
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return await this.mgr
|
||||
}
|
||||
|
||||
close (): void {
|
||||
this.query?.()
|
||||
}
|
||||
|
||||
async notifyTx (...tx: Tx[]): Promise<void> {
|
||||
await this.lq.tx(...tx)
|
||||
}
|
||||
|
||||
getAttrClass (): Ref<Class<Doc>> {
|
||||
return tracker.class.Component
|
||||
}
|
||||
|
||||
async categorize (target: Array<Ref<Doc>>, attr: AnyAttribute): Promise<Array<Ref<Doc>>> {
|
||||
const mgr = await this.getManager()
|
||||
for (const sid of [...target]) {
|
||||
const c = mgr.getIdMap().get(sid as Ref<Component>) as WithLookup<Component>
|
||||
if (c !== undefined) {
|
||||
let components = mgr.getDocs()
|
||||
components = components.filter(
|
||||
(it) => it.label.toLowerCase().trim() === c.label.toLowerCase().trim() && it._id !== c._id
|
||||
)
|
||||
target.push(...components.map((it) => it._id))
|
||||
}
|
||||
}
|
||||
return target.filter((it, idx, arr) => arr.indexOf(it) === idx)
|
||||
}
|
||||
}
|
||||
export const componentStore = writable<DocManager<Component>>(new DocManager([]))
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -136,6 +53,7 @@ export function groupByComponentCategories (categories: any[]): AggregateValue[]
|
||||
|
||||
const usedSpaces = new Set<Ref<Space>>()
|
||||
const componentsList: Array<WithLookup<Component>> = []
|
||||
// console.log('mgr docs', mgr.getDocs())
|
||||
for (const v of categories) {
|
||||
const component = mgr.getIdMap().get(v)
|
||||
if (component !== undefined) {
|
||||
|
@ -30,12 +30,13 @@ import core, {
|
||||
type Ref,
|
||||
type RelatedDocument,
|
||||
type TxOperations,
|
||||
type DocManager,
|
||||
AccountRole
|
||||
} from '@hcengineering/core'
|
||||
import chunter, { type ChatMessage } from '@hcengineering/chunter'
|
||||
import { type Status, translate, type Resources } from '@hcengineering/platform'
|
||||
import { getClient, MessageBox, type ObjectSearchResult } from '@hcengineering/presentation'
|
||||
import { type Issue, type Milestone, type Project } from '@hcengineering/tracker'
|
||||
import { type Component, type Issue, type Milestone, type Project } from '@hcengineering/tracker'
|
||||
import { getCurrentLocation, navigate, showPopup, themeStore } from '@hcengineering/ui'
|
||||
import ComponentEditor from './components/components/ComponentEditor.svelte'
|
||||
import ComponentFilterValuePresenter from './components/components/ComponentFilterValuePresenter.svelte'
|
||||
@ -122,7 +123,7 @@ import ComponentSelector from './components/components/ComponentSelector.svelte'
|
||||
import IssueTemplatePresenter from './components/templates/IssueTemplatePresenter.svelte'
|
||||
import IssueTemplates from './components/templates/IssueTemplates.svelte'
|
||||
|
||||
import { deleteObject, deleteObjects } from '@hcengineering/view-resources'
|
||||
import { deleteObject, deleteObjects, AggregationManager } from '@hcengineering/view-resources'
|
||||
import MoveAndDeleteMilestonePopup from './components/milestones/MoveAndDeleteMilestonePopup.svelte'
|
||||
import EditIssueTemplate from './components/templates/EditIssueTemplate.svelte'
|
||||
import TemplateEstimationEditor from './components/templates/EstimationEditor.svelte'
|
||||
@ -143,7 +144,7 @@ import {
|
||||
subIssueQuery
|
||||
} from './utils'
|
||||
|
||||
import { ComponentAggregationManager, grouppingComponentManager } from './component'
|
||||
import { componentStore, grouppingComponentManager } from './component'
|
||||
import PriorityIcon from './components/activity/PriorityIcon.svelte'
|
||||
import StatusIcon from './components/activity/StatusIcon.svelte'
|
||||
import DeleteComponentPresenter from './components/components/DeleteComponentPresenter.svelte'
|
||||
@ -591,6 +592,14 @@ export async function importTasks (tasks: File, space: Ref<Project>): Promise<vo
|
||||
}
|
||||
}
|
||||
|
||||
function filterComponents (doc: Component, target: Component): boolean {
|
||||
return doc.label.toLowerCase().trim() === target.label.toLowerCase().trim() && doc._id !== target._id
|
||||
}
|
||||
|
||||
function setStore (manager: DocManager<Component>): void {
|
||||
componentStore.set(manager)
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
activity: {
|
||||
PriorityIcon,
|
||||
@ -710,7 +719,9 @@ export default async (): Promise<Resources> => ({
|
||||
GetVisibleFilters: getVisibleFilters,
|
||||
IssueChatTitleProvider: getIssueChatTitle,
|
||||
IsProjectJoined: async (project: Project) => project.members.includes(getCurrentAccount()._id),
|
||||
GetIssueStatusCategories: getIssueStatusCategories
|
||||
GetIssueStatusCategories: getIssueStatusCategories,
|
||||
SetComponentStore: setStore,
|
||||
ComponentFilterFunction: filterComponents
|
||||
},
|
||||
actionImpl: {
|
||||
Move: move,
|
||||
@ -726,7 +737,7 @@ export default async (): Promise<Resources> => ({
|
||||
},
|
||||
aggregation: {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
CreateComponentAggregationManager: ComponentAggregationManager.create,
|
||||
CreateComponentAggregationManager: AggregationManager.create,
|
||||
GrouppingComponentManager: grouppingComponentManager
|
||||
}
|
||||
})
|
||||
|
@ -21,8 +21,6 @@ import {
|
||||
CollectionSize,
|
||||
Data,
|
||||
Doc,
|
||||
DocManager,
|
||||
IdMap,
|
||||
Markup,
|
||||
Mixin,
|
||||
Ref,
|
||||
@ -30,8 +28,7 @@ import {
|
||||
Space,
|
||||
Status,
|
||||
Timestamp,
|
||||
Type,
|
||||
WithLookup
|
||||
Type
|
||||
} from '@hcengineering/core'
|
||||
import { Asset, IntlString, Plugin, Resource, plugin } from '@hcengineering/platform'
|
||||
import { Preference } from '@hcengineering/preference'
|
||||
@ -351,29 +348,6 @@ export interface Component extends Doc {
|
||||
attachments?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Allow to query for status keys/values.
|
||||
*/
|
||||
export class ComponentManager extends DocManager {
|
||||
get (ref: Ref<WithLookup<Component>>): WithLookup<Component> | undefined {
|
||||
return this.getIdMap().get(ref) as WithLookup<Component>
|
||||
}
|
||||
|
||||
getDocs (): Array<WithLookup<Component>> {
|
||||
return this.docs as Component[]
|
||||
}
|
||||
|
||||
getIdMap (): IdMap<WithLookup<Component>> {
|
||||
return this.byId as IdMap<WithLookup<Component>>
|
||||
}
|
||||
|
||||
filter (predicate: (value: Component) => boolean): Component[] {
|
||||
return this.getDocs().filter(predicate)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -97,6 +97,7 @@
|
||||
queryNoLookup,
|
||||
(res) => {
|
||||
fastDocs = res
|
||||
// console.log('query, res', queryNoLookup, res)
|
||||
fastQueryIds = new Set(res.map((it) => it._id))
|
||||
},
|
||||
{ ...categoryQueryOptions, limit: 1000 }
|
||||
|
@ -349,130 +349,149 @@
|
||||
|
||||
const listCategory: SvelteComponentTyped[] = []
|
||||
const listListCategory: ListCategory[] = []
|
||||
function getGroupByKey (
|
||||
docKeys: Partial<DocumentQuery<Doc<Space>>>,
|
||||
category: CategoryType,
|
||||
resultQuery: DocumentQuery<Doc<Space>>
|
||||
): Partial<DocumentQuery<Doc>> {
|
||||
return {
|
||||
...docKeys,
|
||||
[groupByKey]:
|
||||
typeof category === 'object'
|
||||
? category.name !== undefined
|
||||
? { $in: category.values.flatMap((x) => x._id) }
|
||||
: resultQuery[groupByKey]?.$in?.length !== 0
|
||||
? undefined
|
||||
: []
|
||||
: category
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each categories as category, i (typeof category === 'object' ? category.name : category)}
|
||||
{@const items = groupByKey === noCategory ? docs : getGroupByValues(groupByDocs, category)}
|
||||
{@const categoryDocKeys = { ...docKeys, [groupByKey]: category }}
|
||||
<ListCategory
|
||||
bind:this={listListCategory[i]}
|
||||
{extraHeaders}
|
||||
{space}
|
||||
{selectedObjectIds}
|
||||
{headerComponent}
|
||||
{baseMenuClass}
|
||||
{level}
|
||||
{viewOptions}
|
||||
{groupByKey}
|
||||
{lookup}
|
||||
{config}
|
||||
{configurations}
|
||||
{configurationsVersion}
|
||||
{itemModels}
|
||||
{_class}
|
||||
parentCategories={categories.length}
|
||||
groupPersistKey={`${groupPersistKey}_${level}_${typeof category === 'object' ? category.name : category}`}
|
||||
singleCat={level === 0 && categories.length === 1}
|
||||
oneCat={viewOptions.groupBy.length === 1}
|
||||
lastCat={i === categories.length - 1}
|
||||
{category}
|
||||
itemProj={items}
|
||||
docKeys={categoryDocKeys}
|
||||
{newObjectProps}
|
||||
{createItemDialog}
|
||||
{createItemDialogProps}
|
||||
{createItemLabel}
|
||||
{viewOptionsConfig}
|
||||
{compactMode}
|
||||
{resultQuery}
|
||||
{resultOptions}
|
||||
{limiter}
|
||||
{listProvider}
|
||||
on:check
|
||||
on:uncheckAll
|
||||
on:row-focus
|
||||
on:dragstart={(e) => {
|
||||
dispatch('dragstart', {
|
||||
target: e.detail.target,
|
||||
index: e.detail.index + getInitIndex(categories, i)
|
||||
})
|
||||
}}
|
||||
on:collapsed
|
||||
{flatHeaders}
|
||||
{disableHeader}
|
||||
{props}
|
||||
{listDiv}
|
||||
bind:dragItem
|
||||
>
|
||||
<svelte:fragment
|
||||
slot="category"
|
||||
let:docs
|
||||
let:_class
|
||||
let:space
|
||||
let:lookup
|
||||
let:baseMenuClass
|
||||
let:config
|
||||
let:selectedObjectIds
|
||||
let:createItemDialog
|
||||
let:createItemLabel
|
||||
let:viewOptions
|
||||
let:newObjectProps
|
||||
let:flatHeaders
|
||||
let:props
|
||||
let:level
|
||||
let:viewOptionsConfig
|
||||
let:listDiv
|
||||
let:dragstart
|
||||
{@const categoryDocKeys = getGroupByKey(docKeys, category, resultQuery)}
|
||||
{#if items.length !== 0}
|
||||
<ListCategory
|
||||
bind:this={listListCategory[i]}
|
||||
{extraHeaders}
|
||||
{space}
|
||||
{selectedObjectIds}
|
||||
{headerComponent}
|
||||
{baseMenuClass}
|
||||
{level}
|
||||
{viewOptions}
|
||||
{groupByKey}
|
||||
{lookup}
|
||||
{config}
|
||||
{configurations}
|
||||
{configurationsVersion}
|
||||
{itemModels}
|
||||
{_class}
|
||||
parentCategories={categories.length}
|
||||
groupPersistKey={`${groupPersistKey}_${level}_${typeof category === 'object' ? category.name : category}`}
|
||||
singleCat={level === 0 && categories.length === 1}
|
||||
oneCat={viewOptions.groupBy.length === 1}
|
||||
lastCat={i === categories.length - 1}
|
||||
{category}
|
||||
itemProj={items}
|
||||
docKeys={categoryDocKeys}
|
||||
{newObjectProps}
|
||||
{createItemDialog}
|
||||
{createItemDialogProps}
|
||||
{createItemLabel}
|
||||
{viewOptionsConfig}
|
||||
{compactMode}
|
||||
{resultQuery}
|
||||
{resultOptions}
|
||||
{limiter}
|
||||
{listProvider}
|
||||
on:check
|
||||
on:uncheckAll
|
||||
on:row-focus
|
||||
on:dragstart={(e) => {
|
||||
dispatch('dragstart', {
|
||||
target: e.detail.target,
|
||||
index: e.detail.index + getInitIndex(categories, i)
|
||||
})
|
||||
}}
|
||||
on:collapsed
|
||||
{flatHeaders}
|
||||
{disableHeader}
|
||||
{props}
|
||||
{listDiv}
|
||||
bind:dragItem
|
||||
>
|
||||
<svelte:self
|
||||
{docs}
|
||||
bind:this={listCategory[i]}
|
||||
{_class}
|
||||
{space}
|
||||
{lookup}
|
||||
{baseMenuClass}
|
||||
{config}
|
||||
{selectedObjectIds}
|
||||
{createItemDialog}
|
||||
{createItemLabel}
|
||||
{viewOptions}
|
||||
{newObjectProps}
|
||||
{flatHeaders}
|
||||
{props}
|
||||
{level}
|
||||
docKeys={categoryDocKeys}
|
||||
groupPersistKey={`${groupPersistKey}_${level}_${typeof category === 'object' ? category.name : category}`}
|
||||
{initIndex}
|
||||
{viewOptionsConfig}
|
||||
{listDiv}
|
||||
{resultQuery}
|
||||
{resultOptions}
|
||||
{limiter}
|
||||
{listProvider}
|
||||
bind:dragItem
|
||||
on:dragItem
|
||||
on:check
|
||||
on:uncheckAll
|
||||
on:row-focus
|
||||
on:dragstart={dragstart}
|
||||
on:select={(evt) => {
|
||||
select(0, evt.detail)
|
||||
}}
|
||||
on:select-next={(evt) => {
|
||||
if (level !== 0) {
|
||||
dispatch('select-next', evt.detail)
|
||||
} else {
|
||||
select(2, evt.detail)
|
||||
}
|
||||
}}
|
||||
on:select-prev={(evt) => {
|
||||
if (level !== 0) {
|
||||
dispatch('select-prev', evt.detail)
|
||||
} else {
|
||||
select(-2, evt.detail)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</ListCategory>
|
||||
<svelte:fragment
|
||||
slot="category"
|
||||
let:docs
|
||||
let:_class
|
||||
let:space
|
||||
let:lookup
|
||||
let:baseMenuClass
|
||||
let:config
|
||||
let:selectedObjectIds
|
||||
let:createItemDialog
|
||||
let:createItemLabel
|
||||
let:viewOptions
|
||||
let:newObjectProps
|
||||
let:flatHeaders
|
||||
let:props
|
||||
let:level
|
||||
let:viewOptionsConfig
|
||||
let:listDiv
|
||||
let:dragstart
|
||||
>
|
||||
<svelte:self
|
||||
{docs}
|
||||
bind:this={listCategory[i]}
|
||||
{_class}
|
||||
{space}
|
||||
{lookup}
|
||||
{baseMenuClass}
|
||||
{config}
|
||||
{selectedObjectIds}
|
||||
{createItemDialog}
|
||||
{createItemLabel}
|
||||
{viewOptions}
|
||||
{newObjectProps}
|
||||
{flatHeaders}
|
||||
{props}
|
||||
{level}
|
||||
docKeys={categoryDocKeys}
|
||||
groupPersistKey={`${groupPersistKey}_${level}_${typeof category === 'object' ? category.name : category}`}
|
||||
{initIndex}
|
||||
{viewOptionsConfig}
|
||||
{listDiv}
|
||||
{resultQuery}
|
||||
{resultOptions}
|
||||
{limiter}
|
||||
{listProvider}
|
||||
bind:dragItem
|
||||
on:dragItem
|
||||
on:check
|
||||
on:uncheckAll
|
||||
on:row-focus
|
||||
on:dragstart={dragstart}
|
||||
on:select={(evt) => {
|
||||
select(0, evt.detail)
|
||||
}}
|
||||
on:select-next={(evt) => {
|
||||
if (level !== 0) {
|
||||
dispatch('select-next', evt.detail)
|
||||
} else {
|
||||
select(2, evt.detail)
|
||||
}
|
||||
}}
|
||||
on:select-prev={(evt) => {
|
||||
if (level !== 0) {
|
||||
dispatch('select-prev', evt.detail)
|
||||
} else {
|
||||
select(-2, evt.detail)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</ListCategory>
|
||||
{/if}
|
||||
{/each}
|
||||
|
@ -20,7 +20,7 @@ import core, {
|
||||
} from '@hcengineering/core'
|
||||
import { getResource, translate } from '@hcengineering/platform'
|
||||
import { BasePresentationMiddleware, type PresentationMiddleware } from '@hcengineering/presentation'
|
||||
import view, { type AggregationManager } from '@hcengineering/view'
|
||||
import view, { type IAggregationManager } from '@hcengineering/view'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -39,7 +39,7 @@ export interface DocSubScriber<T extends Doc = Doc> {
|
||||
* @public
|
||||
*/
|
||||
export class AggregationMiddleware extends BasePresentationMiddleware implements PresentationMiddleware {
|
||||
mgrs: Map<Ref<Class<Doc>>, AggregationManager> = new Map<Ref<Class<Doc>>, AggregationManager>()
|
||||
mgrs: Map<Ref<Class<Doc>>, IAggregationManager<any>> = new Map<Ref<Class<Doc>>, IAggregationManager<any>>()
|
||||
docs: Doc[] | undefined
|
||||
|
||||
subscribers: Map<string, DocSubScriber> = new Map<string, DocSubScriber>()
|
||||
@ -121,17 +121,30 @@ export class AggregationMiddleware extends BasePresentationMiddleware implements
|
||||
return { unsubscribe: ret.unsubscribe }
|
||||
}
|
||||
|
||||
private async getAggregationManager (_class: Ref<Class<Doc>>): Promise<AggregationManager | undefined> {
|
||||
private async getAggregationManager (_class: Ref<Class<Doc>>): Promise<IAggregationManager<any> | undefined> {
|
||||
let mgr = this.mgrs.get(_class)
|
||||
|
||||
if (mgr === undefined) {
|
||||
const h = this.client.getHierarchy()
|
||||
const mixin = h.classHierarchyMixin(_class, view.mixin.Aggregation)
|
||||
if (mixin?.createAggregationManager !== undefined) {
|
||||
if (
|
||||
mixin?.createAggregationManager !== undefined &&
|
||||
mixin?.setStoreFunc !== undefined &&
|
||||
mixin?.filterFunc !== undefined &&
|
||||
mixin?._class !== undefined
|
||||
) {
|
||||
const f = await getResource(mixin.createAggregationManager)
|
||||
mgr = f(this.client, () => {
|
||||
this.refreshSubscribers()
|
||||
})
|
||||
const storeFunc = await getResource(mixin.setStoreFunc)
|
||||
const filterFunc = await getResource(mixin.filterFunc)
|
||||
mgr = f(
|
||||
this.client,
|
||||
() => {
|
||||
this.refreshSubscribers()
|
||||
},
|
||||
storeFunc,
|
||||
filterFunc,
|
||||
_class
|
||||
)
|
||||
this.mgrs.set(_class, mgr)
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,12 @@ import core, {
|
||||
type TxOperations,
|
||||
type TxUpdateDoc,
|
||||
type TypeAny,
|
||||
type TypedSpace
|
||||
type TypedSpace,
|
||||
type WithLookup,
|
||||
type AnyAttribute,
|
||||
DocManager,
|
||||
SortingOrder,
|
||||
type Tx
|
||||
} from '@hcengineering/core'
|
||||
import { type Restrictions } from '@hcengineering/guest'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
@ -63,6 +68,7 @@ import {
|
||||
isAdminUser,
|
||||
type KeyedAttribute
|
||||
} from '@hcengineering/presentation'
|
||||
import { LiveQuery } from '@hcengineering/query'
|
||||
import { type CollaborationUser } from '@hcengineering/text-editor'
|
||||
import {
|
||||
ErrorPresenter,
|
||||
@ -79,6 +85,7 @@ import {
|
||||
type Location
|
||||
} from '@hcengineering/ui'
|
||||
import view, {
|
||||
type IAggregationManager,
|
||||
AttributeCategoryOrder,
|
||||
type AttributeCategory,
|
||||
type AttributeModel,
|
||||
@ -105,6 +112,102 @@ export interface LoadingProps {
|
||||
length: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class AggregationManager<T extends Doc> implements IAggregationManager<T> {
|
||||
docs: T[] | undefined
|
||||
mgr: DocManager<T> | Promise<DocManager<T>> | undefined
|
||||
query: (() => void) | undefined
|
||||
lq: LiveQuery
|
||||
lqCallback: () => void
|
||||
private readonly setStore: (manager: DocManager<T>) => void
|
||||
private readonly filter: (doc: T, target: T) => boolean
|
||||
private readonly _class: Ref<Class<T>>
|
||||
|
||||
private constructor (
|
||||
client: Client,
|
||||
lqCallback: () => void,
|
||||
setStore: (manager: DocManager<T>) => void,
|
||||
categorizingFunc: (doc: T, target: T) => boolean,
|
||||
_class: Ref<Class<T>>
|
||||
) {
|
||||
this.lq = new LiveQuery(client)
|
||||
this.lqCallback = lqCallback ?? (() => {})
|
||||
this.setStore = setStore
|
||||
this.filter = categorizingFunc
|
||||
this._class = _class
|
||||
void this.getManager()
|
||||
}
|
||||
|
||||
static create<T extends Doc>(
|
||||
client: Client,
|
||||
lqCallback: () => void,
|
||||
setStore: (manager: DocManager<T>) => void,
|
||||
categorizingFunc: (doc: T, target: T) => boolean,
|
||||
_class: Ref<Class<T>>
|
||||
): AggregationManager<T> {
|
||||
return new AggregationManager<T>(client, lqCallback, setStore, categorizingFunc, _class)
|
||||
}
|
||||
|
||||
private async getManager (): Promise<DocManager<T>> {
|
||||
if (this.mgr !== undefined) {
|
||||
if (this.mgr instanceof Promise) {
|
||||
this.mgr = await this.mgr
|
||||
}
|
||||
return this.mgr
|
||||
}
|
||||
this.mgr = new Promise<DocManager<T>>((resolve) => {
|
||||
this.query = this.lq.query(
|
||||
this._class,
|
||||
{},
|
||||
(res) => {
|
||||
const first = this.docs === undefined
|
||||
this.docs = res
|
||||
this.mgr = new DocManager<T>(res as T[])
|
||||
this.setStore(this.mgr)
|
||||
if (!first) {
|
||||
this.lqCallback()
|
||||
}
|
||||
resolve(this.mgr)
|
||||
},
|
||||
{
|
||||
sort: {
|
||||
label: SortingOrder.Ascending
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return await this.mgr
|
||||
}
|
||||
|
||||
close (): void {
|
||||
this.query?.()
|
||||
}
|
||||
|
||||
async notifyTx (...tx: Tx[]): Promise<void> {
|
||||
await this.lq.tx(...tx)
|
||||
}
|
||||
|
||||
getAttrClass (): Ref<Class<T>> {
|
||||
return this._class
|
||||
}
|
||||
|
||||
async categorize (target: Array<Ref<T>>, attr: AnyAttribute): Promise<Array<Ref<T>>> {
|
||||
const mgr = await this.getManager()
|
||||
for (const sid of [...target]) {
|
||||
const c = mgr.getIdMap().get(sid) as WithLookup<T>
|
||||
if (c !== undefined) {
|
||||
let docs = mgr.getDocs()
|
||||
docs = docs.filter((it: T) => this.filter(it, c))
|
||||
target.push(...docs.map((it) => it._id))
|
||||
}
|
||||
}
|
||||
return target.filter((it, idx, arr) => arr.indexOf(it) === idx)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
Class,
|
||||
Client,
|
||||
Doc,
|
||||
DocManager,
|
||||
DocumentQuery,
|
||||
FindOptions,
|
||||
Hierarchy,
|
||||
@ -373,29 +374,39 @@ export interface Groupping extends Class<Doc> {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface AggregationManager {
|
||||
export interface IAggregationManager<T extends Doc> {
|
||||
close: () => void
|
||||
notifyTx: (...tx: Tx[]) => Promise<void>
|
||||
categorize: (target: Array<Ref<Doc>>, attr: AnyAttribute) => Promise<Array<Ref<Doc>>>
|
||||
getAttrClass: () => Ref<Class<Doc>>
|
||||
updateSorting?: (finalOptions: FindOptions<Doc>, attr: AnyAttribute) => Promise<void>
|
||||
categorize: (target: Array<Ref<T>>, attr: AnyAttribute) => Promise<Array<Ref<T>>>
|
||||
getAttrClass: () => Ref<Class<T>>
|
||||
updateSorting?: (finalOptions: FindOptions<T>, attr: AnyAttribute) => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type AggregationManagerResource = Resource<AggregationManager>
|
||||
export type AggregationManagerResource = Resource<IAggregationManager<any>>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type CreateAggregationManagerFunc = Resource<(client: Client, lqCallback: () => void) => AggregationManager>
|
||||
export type CreateAggregationManagerFunc = Resource<
|
||||
(
|
||||
client: Client,
|
||||
lqCallback: () => void,
|
||||
setStore: (manager: DocManager<any>) => void,
|
||||
categorizingFunc: (doc: any, target: any) => boolean,
|
||||
_class: Ref<Class<any>>
|
||||
) => IAggregationManager<any>
|
||||
>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Aggregation extends Class<Doc> {
|
||||
createAggregationManager: CreateAggregationManagerFunc
|
||||
setStoreFunc: Resource<(manager: DocManager<any>) => void>
|
||||
filterFunc: Resource<(doc: Doc, target: Doc) => boolean>
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user