mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-03 08:57:14 +03:00
[UBER-197] Aggregate components properly (#3265)
Signed-off-by: Ruslan Bayandinov <wazsone@ya.ru>
This commit is contained in:
parent
00df8de669
commit
20da8625d2
@ -19743,7 +19743,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/model-view.tgz_typescript@4.8.4:
|
file:projects/model-view.tgz_typescript@4.8.4:
|
||||||
resolution: {integrity: sha512-MXdgL834gD1r+h6dZaXCpGCSiHrO2TxGXDHfF951mTThYcbz2X5P950/XcADZgvC4riAXQoJRVCjn1iR1paanA==, tarball: file:projects/model-view.tgz}
|
resolution: {integrity: sha512-nswV/jP/DzqP9yt1TnthbVdziRUYEW3rs/mqG3VTSIwO0Z0Sy0hGbFDr21n8YjxBf756QK3qAItBlJwRq4N3Tg==, tarball: file:projects/model-view.tgz}
|
||||||
id: file:projects/model-view.tgz
|
id: file:projects/model-view.tgz
|
||||||
name: '@rush-temp/model-view'
|
name: '@rush-temp/model-view'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
@ -22070,7 +22070,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/tracker-resources.tgz_a1d864769aaf53d09b76fe134ab55e60:
|
file:projects/tracker-resources.tgz_a1d864769aaf53d09b76fe134ab55e60:
|
||||||
resolution: {integrity: sha512-jMfAS0PwGvid0QCtAiVe3bDOLx4PnRdtmz23urZXRSf+s9mtZNpKIGPNDic8wragI9GjAbjJ85bHt8fZaxsSlw==, tarball: file:projects/tracker-resources.tgz}
|
resolution: {integrity: sha512-aahQ2kYGfdS+98z6xeLjeFffJYcfU106RpeslK2u9g9zaAPmdZa6L72ZRFiEDkW1y6n2d3TpDasCCshMw2lEHg==, tarball: file:projects/tracker-resources.tgz}
|
||||||
id: file:projects/tracker-resources.tgz
|
id: file:projects/tracker-resources.tgz
|
||||||
name: '@rush-temp/tracker-resources'
|
name: '@rush-temp/tracker-resources'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
@ -22210,7 +22210,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/view-resources.tgz_a1d864769aaf53d09b76fe134ab55e60:
|
file:projects/view-resources.tgz_a1d864769aaf53d09b76fe134ab55e60:
|
||||||
resolution: {integrity: sha512-jeJXZ80aZypm/6JrOsW+XJ6z8mzAUf7CJpVfPP8qTYIV2FsTqYBY4aHK0roPkaUVEAd/Ox9oAwKDksoR8EgB/w==, tarball: file:projects/view-resources.tgz}
|
resolution: {integrity: sha512-Taw9Dsr8rIkwsDJBqW4FddqW78UyIqHptCBk+J2c4nJlYnEUYKzbJaszb0N/fOkTN43I72SoxOQlyygIH85sKw==, tarball: file:projects/view-resources.tgz}
|
||||||
id: file:projects/view-resources.tgz
|
id: file:projects/view-resources.tgz
|
||||||
name: '@rush-temp/view-resources'
|
name: '@rush-temp/view-resources'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
@ -22245,7 +22245,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/view.tgz:
|
file:projects/view.tgz:
|
||||||
resolution: {integrity: sha512-BKoOzFnUY/wlXgnnf7GsXZi9v3rZao+9EUVJznob9Dd0PK919rBLq/3XtXduTsX9I5XL63X6aThlzp6BpUyCNQ==, tarball: file:projects/view.tgz}
|
resolution: {integrity: sha512-Q5nEEe6n/qr7hJMMUeD2WSC2/MDdStM4h1maedLCV4OB6t2x0HTmNxm7b8mjROeuTV9lsxUezlMuq2xnBbF7ug==, tarball: file:projects/view.tgz}
|
||||||
name: '@rush-temp/view'
|
name: '@rush-temp/view'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -22259,6 +22259,7 @@ packages:
|
|||||||
eslint-plugin-n: 15.5.1_eslint@8.27.0
|
eslint-plugin-n: 15.5.1_eslint@8.27.0
|
||||||
eslint-plugin-promise: 6.1.1_eslint@8.27.0
|
eslint-plugin-promise: 6.1.1_eslint@8.27.0
|
||||||
prettier: 2.8.8
|
prettier: 2.8.8
|
||||||
|
svelte: 3.55.1
|
||||||
typescript: 4.8.4
|
typescript: 4.8.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
@ -16,10 +16,11 @@
|
|||||||
import { DOMAIN_MODEL } from '@hcengineering/core'
|
import { DOMAIN_MODEL } from '@hcengineering/core'
|
||||||
import { Builder, Model } from '@hcengineering/model'
|
import { Builder, Model } from '@hcengineering/model'
|
||||||
import core, { TDoc } from '@hcengineering/model-core'
|
import core, { TDoc } from '@hcengineering/model-core'
|
||||||
import type { Asset, IntlString, Resource } from '@hcengineering/platform'
|
import { Asset, IntlString, Resource } from '@hcengineering/platform'
|
||||||
// Import types to prevent .svelte components to being exposed to type typescript.
|
// Import types to prevent .svelte components to being exposed to type typescript.
|
||||||
import { ObjectSearchCategory, ObjectSearchFactory } from '@hcengineering/presentation/src/types'
|
import { ObjectSearchCategory, ObjectSearchFactory } from '@hcengineering/presentation/src/types'
|
||||||
import presentation from './plugin'
|
import presentation from './plugin'
|
||||||
|
import { PresentationMiddlewareCreator, PresentationMiddlewareFactory } from '@hcengineering/presentation'
|
||||||
|
|
||||||
export { presentationId } from '@hcengineering/presentation/src/plugin'
|
export { presentationId } from '@hcengineering/presentation/src/plugin'
|
||||||
export { default } from './plugin'
|
export { default } from './plugin'
|
||||||
@ -34,6 +35,11 @@ export class TObjectSearchCategory extends TDoc implements ObjectSearchCategory
|
|||||||
query!: Resource<ObjectSearchFactory>
|
query!: Resource<ObjectSearchFactory>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
@Model(presentation.class.PresentationMiddlewareFactory, core.class.Doc, DOMAIN_MODEL)
|
||||||
builder.createModel(TObjectSearchCategory)
|
export class TPresentationMiddlewareFactory extends TDoc implements PresentationMiddlewareFactory {
|
||||||
|
createPresentationMiddleware!: Resource<PresentationMiddlewareCreator>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createModel (builder: Builder): void {
|
||||||
|
builder.createModel(TObjectSearchCategory, TPresentationMiddlewareFactory)
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import activity from '@hcengineering/activity'
|
import activity from '@hcengineering/activity'
|
||||||
import type { Employee, EmployeeAccount } from '@hcengineering/contact'
|
import contact, { Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||||
import contact from '@hcengineering/contact'
|
|
||||||
import {
|
import {
|
||||||
DOMAIN_MODEL,
|
DOMAIN_MODEL,
|
||||||
DateRangeMode,
|
DateRangeMode,
|
||||||
@ -1011,6 +1010,18 @@ export function createModel (builder: Builder): void {
|
|||||||
inlineEditor: tracker.component.ComponentSelector
|
inlineEditor: tracker.component.ComponentSelector
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.AttributePresenter, {
|
||||||
|
presenter: tracker.component.ComponentRefPresenter
|
||||||
|
})
|
||||||
|
|
||||||
|
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.Aggregation, {
|
||||||
|
createAggregationManager: tracker.aggregation.CreateComponentAggregationManager
|
||||||
|
})
|
||||||
|
|
||||||
|
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.Groupping, {
|
||||||
|
grouppingManager: tracker.aggregation.GrouppingComponentManager
|
||||||
|
})
|
||||||
|
|
||||||
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.ObjectPresenter, {
|
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.ObjectPresenter, {
|
||||||
presenter: tracker.component.MilestonePresenter
|
presenter: tracker.component.MilestonePresenter
|
||||||
})
|
})
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"@hcengineering/model-core": "^0.6.0",
|
"@hcengineering/model-core": "^0.6.0",
|
||||||
"@hcengineering/preference": "^0.6.6",
|
"@hcengineering/preference": "^0.6.6",
|
||||||
"@hcengineering/model-preference": "^0.6.0",
|
"@hcengineering/model-preference": "^0.6.0",
|
||||||
"@hcengineering/model-presentation": "^0.6.0"
|
"@hcengineering/model-presentation": "^0.6.0",
|
||||||
|
"@hcengineering/presentation": "^0.6.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,14 +13,24 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Account, Class, Client, Data, Doc, DocumentQuery, Domain, Ref, Space } from '@hcengineering/core'
|
import Core, {
|
||||||
import { DOMAIN_MODEL } from '@hcengineering/core'
|
DOMAIN_MODEL,
|
||||||
|
Account,
|
||||||
|
Class,
|
||||||
|
Client,
|
||||||
|
Data,
|
||||||
|
Doc,
|
||||||
|
DocumentQuery,
|
||||||
|
Domain,
|
||||||
|
Ref,
|
||||||
|
Space
|
||||||
|
} from '@hcengineering/core'
|
||||||
import { Builder, Mixin, Model } from '@hcengineering/model'
|
import { Builder, Mixin, Model } from '@hcengineering/model'
|
||||||
import core, { TClass, TDoc } from '@hcengineering/model-core'
|
import core, { TClass, TDoc } from '@hcengineering/model-core'
|
||||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||||
import type { Asset, IntlString, Resource, Status } from '@hcengineering/platform'
|
import { Asset, IntlString, Resource, Status } from '@hcengineering/platform'
|
||||||
import type { AnyComponent, Location } from '@hcengineering/ui'
|
import { AnyComponent, Location } from '@hcengineering/ui'
|
||||||
import type {
|
import {
|
||||||
Action,
|
Action,
|
||||||
ActionCategory,
|
ActionCategory,
|
||||||
ActivityAttributePresenter,
|
ActivityAttributePresenter,
|
||||||
@ -68,8 +78,13 @@ import type {
|
|||||||
ViewOptionsModel,
|
ViewOptionsModel,
|
||||||
Viewlet,
|
Viewlet,
|
||||||
ViewletDescriptor,
|
ViewletDescriptor,
|
||||||
ViewletPreference
|
ViewletPreference,
|
||||||
|
Aggregation,
|
||||||
|
CreateAggregationManagerFunc,
|
||||||
|
GrouppingManagerResource,
|
||||||
|
Groupping
|
||||||
} from '@hcengineering/view'
|
} from '@hcengineering/view'
|
||||||
|
import presentation from '@hcengineering/model-presentation'
|
||||||
import view from './plugin'
|
import view from './plugin'
|
||||||
|
|
||||||
export { viewId } from '@hcengineering/view'
|
export { viewId } from '@hcengineering/view'
|
||||||
@ -260,6 +275,16 @@ export class TAllValuesFunc extends TClass implements AllValuesFunc {
|
|||||||
func!: GetAllValuesFunc
|
func!: GetAllValuesFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mixin(view.mixin.Groupping, core.class.Class)
|
||||||
|
export class TGroupping extends TClass implements Groupping {
|
||||||
|
grouppingManager!: GrouppingManagerResource
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mixin(view.mixin.Aggregation, core.class.Class)
|
||||||
|
export class TAggregation extends TClass implements Aggregation {
|
||||||
|
createAggregationManager!: CreateAggregationManagerFunc
|
||||||
|
}
|
||||||
|
|
||||||
@Model(view.class.ViewletPreference, preference.class.Preference)
|
@Model(view.class.ViewletPreference, preference.class.Preference)
|
||||||
export class TViewletPreference extends TPreference implements ViewletPreference {
|
export class TViewletPreference extends TPreference implements ViewletPreference {
|
||||||
attachedTo!: Ref<Viewlet>
|
attachedTo!: Ref<Viewlet>
|
||||||
@ -408,7 +433,9 @@ export function createModel (builder: Builder): void {
|
|||||||
TArrayEditor,
|
TArrayEditor,
|
||||||
TInlineAttributEditor,
|
TInlineAttributEditor,
|
||||||
TFilteredView,
|
TFilteredView,
|
||||||
TAllValuesFunc
|
TAllValuesFunc,
|
||||||
|
TAggregation,
|
||||||
|
TGroupping
|
||||||
)
|
)
|
||||||
|
|
||||||
classPresenter(
|
classPresenter(
|
||||||
@ -517,6 +544,15 @@ export function createModel (builder: Builder): void {
|
|||||||
view.viewlet.List
|
view.viewlet.List
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder.createDoc(
|
||||||
|
presentation.class.PresentationMiddlewareFactory,
|
||||||
|
core.space.Model,
|
||||||
|
{
|
||||||
|
createPresentationMiddleware: view.function.CreateDocMiddleware
|
||||||
|
},
|
||||||
|
view.pipeline.PresentationMiddleware
|
||||||
|
)
|
||||||
|
|
||||||
createAction(
|
createAction(
|
||||||
builder,
|
builder,
|
||||||
{
|
{
|
||||||
@ -990,6 +1026,14 @@ export function createModel (builder: Builder): void {
|
|||||||
builder.mixin(core.class.Status, core.class.Class, view.mixin.AttributePresenter, {
|
builder.mixin(core.class.Status, core.class.Class, view.mixin.AttributePresenter, {
|
||||||
presenter: view.component.StatusRefPresenter
|
presenter: view.component.StatusRefPresenter
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(Core.class.Status, core.class.Class, view.mixin.Aggregation, {
|
||||||
|
createAggregationManager: view.aggregation.CreateStatusAggregationManager
|
||||||
|
})
|
||||||
|
|
||||||
|
builder.mixin(Core.class.Status, core.class.Class, view.mixin.Groupping, {
|
||||||
|
grouppingManager: view.aggregation.GrouppingStatusManager
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default view
|
export default view
|
||||||
|
@ -13,9 +13,11 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import { Ref } from '@hcengineering/core'
|
||||||
import { IntlString, mergeIds } from '@hcengineering/platform'
|
import { IntlString, mergeIds } from '@hcengineering/platform'
|
||||||
import type { AnyComponent } from '@hcengineering/ui'
|
import { AnyComponent } from '@hcengineering/ui'
|
||||||
import { FilterFunction, ViewAction, ViewCategoryAction, viewId } from '@hcengineering/view'
|
import { FilterFunction, ViewAction, ViewCategoryAction, viewId } from '@hcengineering/view'
|
||||||
|
import { PresentationMiddlewareFactory } from '@hcengineering/presentation'
|
||||||
import view from '@hcengineering/view-resources/src/plugin'
|
import view from '@hcengineering/view-resources/src/plugin'
|
||||||
|
|
||||||
export default mergeIds(viewId, view, {
|
export default mergeIds(viewId, view, {
|
||||||
@ -118,5 +120,8 @@ export default mergeIds(viewId, view, {
|
|||||||
FilterDateNotSpecified: '' as FilterFunction,
|
FilterDateNotSpecified: '' as FilterFunction,
|
||||||
FilterDateCustom: '' as FilterFunction,
|
FilterDateCustom: '' as FilterFunction,
|
||||||
ShowEmptyGroups: '' as ViewCategoryAction
|
ShowEmptyGroups: '' as ViewCategoryAction
|
||||||
|
},
|
||||||
|
pipeline: {
|
||||||
|
PresentationMiddleware: '' as Ref<PresentationMiddlewareFactory>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
|
|
||||||
import { Asset, IntlString } from '@hcengineering/platform'
|
import { Asset, IntlString } from '@hcengineering/platform'
|
||||||
import { Attribute, Doc, Domain, Ref } from './classes'
|
import { Attribute, Doc, Domain, Ref } from './classes'
|
||||||
|
import { AggregateValue, AggregateValueData, DocManager, IdMap } from './utils'
|
||||||
import { WithLookup } from './storage'
|
import { WithLookup } from './storage'
|
||||||
import { IdMap, toIdMap } from './utils'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -60,8 +60,14 @@ export interface Status extends Doc {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export class StatusValue {
|
export class StatusValue extends AggregateValue {
|
||||||
constructor (readonly name: string, readonly color: number | undefined, readonly values: WithLookup<Status>[]) {}
|
constructor (
|
||||||
|
readonly name: string | undefined,
|
||||||
|
readonly color: number | undefined,
|
||||||
|
readonly values: AggregateValueData[]
|
||||||
|
) {
|
||||||
|
super(name, values)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,23 +75,20 @@ export class StatusValue {
|
|||||||
*
|
*
|
||||||
* Allow to query for status keys/values.
|
* Allow to query for status keys/values.
|
||||||
*/
|
*/
|
||||||
export class StatusManager {
|
export class StatusManager extends DocManager {
|
||||||
byId: IdMap<WithLookup<Status>>
|
get (ref: Ref<WithLookup<Status>>): WithLookup<Status> | undefined {
|
||||||
|
return this.getIdMap().get(ref) as WithLookup<Status>
|
||||||
constructor (readonly statuses: WithLookup<Status>[]) {
|
|
||||||
this.byId = toIdMap(statuses)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get (ref: Ref<Status>): WithLookup<Status> | undefined {
|
getDocs (): Array<WithLookup<Status>> {
|
||||||
return this.byId.get(ref)
|
return this.docs as Status[]
|
||||||
}
|
}
|
||||||
|
|
||||||
filter (predicate: (value: WithLookup<Status>) => boolean): WithLookup<Status>[] {
|
getIdMap (): IdMap<WithLookup<Status>> {
|
||||||
return this.statuses.filter(predicate)
|
return this.byId as IdMap<WithLookup<Status>>
|
||||||
|
}
|
||||||
|
|
||||||
|
filter (predicate: (value: Status) => boolean): Status[] {
|
||||||
|
return this.getDocs().filter(predicate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export type CategoryType = number | string | undefined | Ref<Doc> | StatusValue
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { Account, AnyAttribute, Class, Doc, DocData, DocIndexState, IndexKind, Obj, Ref } from './classes'
|
import { Account, AnyAttribute, Class, Doc, DocData, DocIndexState, IndexKind, Obj, Ref, Space } from './classes'
|
||||||
import core from './component'
|
import core from './component'
|
||||||
import { Hierarchy } from './hierarchy'
|
import { Hierarchy } from './hierarchy'
|
||||||
import { FindResult } from './storage'
|
import { FindResult } from './storage'
|
||||||
@ -224,3 +224,59 @@ export function fillDefaults<T extends Doc> (
|
|||||||
}
|
}
|
||||||
return object
|
return object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export class AggregateValueData {
|
||||||
|
constructor (
|
||||||
|
readonly name: string,
|
||||||
|
readonly _id: Ref<Doc>,
|
||||||
|
readonly space: Ref<Space>,
|
||||||
|
readonly rank?: string,
|
||||||
|
readonly category?: Ref<Doc>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
getRank (): string {
|
||||||
|
return this.rank ?? ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export class AggregateValue {
|
||||||
|
constructor (readonly name: string | undefined, readonly values: AggregateValueData[]) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type CategoryType = number | string | undefined | Ref<Doc> | AggregateValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export class DocManager {
|
||||||
|
protected readonly byId: IdMap<Doc>
|
||||||
|
|
||||||
|
constructor (protected readonly docs: Doc[]) {
|
||||||
|
this.byId = toIdMap(docs)
|
||||||
|
}
|
||||||
|
|
||||||
|
get (ref: Ref<Doc>): Doc | undefined {
|
||||||
|
return this.byId.get(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
getDocs (): Doc[] {
|
||||||
|
return this.docs
|
||||||
|
}
|
||||||
|
|
||||||
|
getIdMap (): IdMap<Doc> {
|
||||||
|
return this.byId
|
||||||
|
}
|
||||||
|
|
||||||
|
filter (predicate: (value: Doc) => boolean): Doc[] {
|
||||||
|
return this.docs.filter(predicate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,8 +17,6 @@ import core, { PluginConfiguration, SortingOrder } from '@hcengineering/core'
|
|||||||
import { Plugin, Resource, getResourcePlugin } from '@hcengineering/platform'
|
import { Plugin, Resource, getResourcePlugin } from '@hcengineering/platform'
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
import { createQuery } from '.'
|
import { createQuery } from '.'
|
||||||
import { statusStore } from './status'
|
|
||||||
export { statusStore }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
|
@ -49,6 +49,7 @@ export * from './drafts'
|
|||||||
export { presentationId }
|
export { presentationId }
|
||||||
export * from './configuration'
|
export * from './configuration'
|
||||||
export * from './context'
|
export * from './context'
|
||||||
|
export * from './pipeline'
|
||||||
|
|
||||||
addStringsLoader(presentationId, async (lang: string) => {
|
addStringsLoader(presentationId, async (lang: string) => {
|
||||||
return await import(`../lang/${lang}.json`)
|
return await import(`../lang/${lang}.json`)
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
TxResult,
|
TxResult,
|
||||||
WithLookup
|
WithLookup
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
|
import { Resource } from '@hcengineering/platform'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -232,3 +233,10 @@ export abstract class BasePresentationMiddleware {
|
|||||||
return { unsubscribe: () => {} }
|
return { unsubscribe: () => {} }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface PresentationMiddlewareFactory extends Doc {
|
||||||
|
createPresentationMiddleware: Resource<PresentationMiddlewareCreator>
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ import { Class, Ref } from '@hcengineering/core'
|
|||||||
import type { Asset, IntlString, Metadata, Plugin } from '@hcengineering/platform'
|
import type { Asset, IntlString, Metadata, Plugin } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
import { ObjectSearchCategory } from './types'
|
import { ObjectSearchCategory } from './types'
|
||||||
|
import { PresentationMiddlewareFactory } from './pipeline'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -26,7 +27,8 @@ export const presentationId = 'presentation' as Plugin
|
|||||||
|
|
||||||
export default plugin(presentationId, {
|
export default plugin(presentationId, {
|
||||||
class: {
|
class: {
|
||||||
ObjectSearchCategory: '' as Ref<Class<ObjectSearchCategory>>
|
ObjectSearchCategory: '' as Ref<Class<ObjectSearchCategory>>,
|
||||||
|
PresentationMiddlewareFactory: '' as Ref<Class<PresentationMiddlewareFactory>>
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
Create: '' as IntlString,
|
Create: '' as IntlString,
|
||||||
|
@ -1,316 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright © 2023 Hardcore Engineering Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License. You may
|
|
||||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
//
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
|
|
||||||
import core, {
|
|
||||||
AnyAttribute,
|
|
||||||
Attribute,
|
|
||||||
Class,
|
|
||||||
Client,
|
|
||||||
Doc,
|
|
||||||
DocumentQuery,
|
|
||||||
FindOptions,
|
|
||||||
FindResult,
|
|
||||||
generateId,
|
|
||||||
Hierarchy,
|
|
||||||
Ref,
|
|
||||||
RefTo,
|
|
||||||
SortingOrder,
|
|
||||||
SortingRules,
|
|
||||||
Status,
|
|
||||||
StatusManager,
|
|
||||||
Tx,
|
|
||||||
TxResult
|
|
||||||
} from '@hcengineering/core'
|
|
||||||
import { LiveQuery } from '@hcengineering/query'
|
|
||||||
import { writable } from 'svelte/store'
|
|
||||||
import { BasePresentationMiddleware, PresentationMiddleware } from './pipeline'
|
|
||||||
|
|
||||||
// Issue status live query
|
|
||||||
export const statusStore = writable<StatusManager>(new StatusManager([]))
|
|
||||||
|
|
||||||
interface StatusSubscriber<T extends Doc = Doc> {
|
|
||||||
attributes: Array<Ref<AnyAttribute>>
|
|
||||||
|
|
||||||
_class: Ref<Class<T>>
|
|
||||||
query: DocumentQuery<T>
|
|
||||||
options?: FindOptions<T>
|
|
||||||
|
|
||||||
refresh: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export class StatusMiddleware extends BasePresentationMiddleware implements PresentationMiddleware {
|
|
||||||
mgr: StatusManager | Promise<StatusManager> | undefined
|
|
||||||
status: Status[] | undefined
|
|
||||||
statusQuery: (() => void) | undefined
|
|
||||||
lq: LiveQuery
|
|
||||||
|
|
||||||
subscribers: Map<string, StatusSubscriber> = new Map()
|
|
||||||
private constructor (client: Client, next?: PresentationMiddleware) {
|
|
||||||
super(client, next)
|
|
||||||
this.lq = new LiveQuery(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
async notifyTx (tx: Tx): Promise<void> {
|
|
||||||
await this.lq.tx(tx)
|
|
||||||
await this.provideNotifyTx(tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
async close (): Promise<void> {
|
|
||||||
this.statusQuery?.()
|
|
||||||
return await this.provideClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
async getManager (): Promise<StatusManager> {
|
|
||||||
if (this.mgr !== undefined) {
|
|
||||||
if (this.mgr instanceof Promise) {
|
|
||||||
this.mgr = await this.mgr
|
|
||||||
}
|
|
||||||
return this.mgr
|
|
||||||
}
|
|
||||||
this.mgr = new Promise<StatusManager>((resolve) => {
|
|
||||||
this.statusQuery = this.lq.query(
|
|
||||||
core.class.Status,
|
|
||||||
{},
|
|
||||||
(res) => {
|
|
||||||
const first = this.status === undefined
|
|
||||||
this.status = res
|
|
||||||
this.mgr = new StatusManager(res)
|
|
||||||
statusStore.set(this.mgr)
|
|
||||||
if (!first) {
|
|
||||||
this.refreshSubscribers()
|
|
||||||
}
|
|
||||||
resolve(this.mgr)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lookup: {
|
|
||||||
category: core.class.StatusCategory
|
|
||||||
},
|
|
||||||
sort: {
|
|
||||||
rank: SortingOrder.Ascending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return await this.mgr
|
|
||||||
}
|
|
||||||
|
|
||||||
private refreshSubscribers (): void {
|
|
||||||
for (const s of this.subscribers.values()) {
|
|
||||||
// TODO: Do something more smart and track if used status field is changed.
|
|
||||||
s.refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async subscribe<T extends Doc>(
|
|
||||||
_class: Ref<Class<T>>,
|
|
||||||
query: DocumentQuery<T>,
|
|
||||||
options: FindOptions<T> | undefined,
|
|
||||||
refresh: () => void
|
|
||||||
): Promise<{
|
|
||||||
unsubscribe: () => void
|
|
||||||
query?: DocumentQuery<T>
|
|
||||||
options?: FindOptions<T>
|
|
||||||
}> {
|
|
||||||
const ret = await this.provideSubscribe(_class, query, options, refresh)
|
|
||||||
const h = this.client.getHierarchy()
|
|
||||||
|
|
||||||
const id = generateId()
|
|
||||||
const s: StatusSubscriber<T> = {
|
|
||||||
_class,
|
|
||||||
query,
|
|
||||||
refresh,
|
|
||||||
options,
|
|
||||||
attributes: []
|
|
||||||
}
|
|
||||||
const statusFields: Array<Attribute<Status>> = []
|
|
||||||
const allAttrs = h.getAllAttributes(_class)
|
|
||||||
|
|
||||||
const updatedQuery: DocumentQuery<T> = { ...(ret.query ?? query) }
|
|
||||||
const finalOptions = { ...(ret.options ?? options ?? {}) }
|
|
||||||
|
|
||||||
await this.updateQueryOptions<T>(allAttrs, h, statusFields, updatedQuery, finalOptions)
|
|
||||||
|
|
||||||
if (statusFields.length > 0) {
|
|
||||||
this.subscribers.set(id, s)
|
|
||||||
return {
|
|
||||||
unsubscribe: () => {
|
|
||||||
ret.unsubscribe()
|
|
||||||
this.subscribers.delete(id)
|
|
||||||
},
|
|
||||||
query: updatedQuery,
|
|
||||||
options: finalOptions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { unsubscribe: (await ret).unsubscribe }
|
|
||||||
}
|
|
||||||
|
|
||||||
static create (client: Client, next?: PresentationMiddleware): StatusMiddleware {
|
|
||||||
return new StatusMiddleware(client, next)
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAll<T extends Doc>(
|
|
||||||
_class: Ref<Class<T>>,
|
|
||||||
query: DocumentQuery<T>,
|
|
||||||
options?: FindOptions<T> | undefined
|
|
||||||
): Promise<FindResult<T>> {
|
|
||||||
const statusFields: Array<Attribute<Status>> = []
|
|
||||||
const h = this.client.getHierarchy()
|
|
||||||
const allAttrs = h.getAllAttributes(_class)
|
|
||||||
const finalOptions = options ?? {}
|
|
||||||
|
|
||||||
await this.updateQueryOptions<T>(allAttrs, h, statusFields, query, finalOptions)
|
|
||||||
|
|
||||||
const result = await this.provideFindAll(_class, query, finalOptions)
|
|
||||||
// We need to add $
|
|
||||||
if (statusFields.length > 0) {
|
|
||||||
// We need to update $lookup for status fields and provide $status group fields.
|
|
||||||
for (const attr of statusFields) {
|
|
||||||
for (const r of result) {
|
|
||||||
const resultDoc = Hierarchy.toDoc(r)
|
|
||||||
if (resultDoc.$lookup === undefined) {
|
|
||||||
resultDoc.$lookup = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check for mixin?
|
|
||||||
const stateValue = (r as any)[attr.name]
|
|
||||||
const status = (await this.getManager()).byId.get(stateValue)
|
|
||||||
if (status !== undefined) {
|
|
||||||
;(resultDoc.$lookup as any)[attr.name] = status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private categorizeStatus (mgr: StatusManager, attr: AnyAttribute, target: Array<Ref<Status>>): Array<Ref<Status>> {
|
|
||||||
for (const sid of [...target]) {
|
|
||||||
const s = mgr.byId.get(sid)
|
|
||||||
if (s !== undefined) {
|
|
||||||
const statuses = mgr.statuses.filter(
|
|
||||||
(it) =>
|
|
||||||
it.ofAttribute === attr._id &&
|
|
||||||
it.name.toLowerCase().trim() === s.name.toLowerCase().trim() &&
|
|
||||||
it._id !== s._id
|
|
||||||
)
|
|
||||||
target.push(...statuses.map((it) => it._id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return target.filter((it, idx, arr) => arr.indexOf(it) === idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
private async updateQueryOptions<T extends Doc>(
|
|
||||||
allAttrs: Map<string, AnyAttribute>,
|
|
||||||
h: Hierarchy,
|
|
||||||
statusFields: Array<Attribute<Status>>,
|
|
||||||
query: DocumentQuery<T>,
|
|
||||||
finalOptions: FindOptions<T>
|
|
||||||
): Promise<void> {
|
|
||||||
for (const attr of allAttrs.values()) {
|
|
||||||
try {
|
|
||||||
if (attr.type._class === core.class.RefTo && h.isDerived((attr.type as RefTo<Doc>).to, core.class.Status)) {
|
|
||||||
const mgr = await this.getManager()
|
|
||||||
let target: Array<Ref<Status>> = []
|
|
||||||
let targetNin: Array<Ref<Status>> = []
|
|
||||||
statusFields.push(attr)
|
|
||||||
const v = (query as any)[attr.name]
|
|
||||||
|
|
||||||
if (v != null) {
|
|
||||||
// Only add filter if we have filer inside.
|
|
||||||
if (typeof v === 'string') {
|
|
||||||
target.push(v as Ref<Status>)
|
|
||||||
} else {
|
|
||||||
if (v.$in !== undefined) {
|
|
||||||
target.push(...v.$in)
|
|
||||||
} else if (v.$nin !== undefined) {
|
|
||||||
targetNin.push(...v.$nin)
|
|
||||||
} else if (v.$ne !== undefined) {
|
|
||||||
targetNin.push(v.$ne)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all similar name statues for same attribute name.
|
|
||||||
target = this.categorizeStatus(mgr, attr, target)
|
|
||||||
targetNin = this.categorizeStatus(mgr, attr, targetNin)
|
|
||||||
if (target.length > 0 || targetNin.length > 0) {
|
|
||||||
;(query as any)[attr.name] = {}
|
|
||||||
if (target.length > 0) {
|
|
||||||
;(query as any)[attr.name].$in = target
|
|
||||||
}
|
|
||||||
if (targetNin.length > 0) {
|
|
||||||
;(query as any)[attr.name].$nin = targetNin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (finalOptions.lookup !== undefined) {
|
|
||||||
// Remove lookups by status field
|
|
||||||
if ((finalOptions.lookup as any)[attr.name] !== undefined) {
|
|
||||||
const { [attr.name]: _, ...newLookup } = finalOptions.lookup as any
|
|
||||||
finalOptions.lookup = newLookup
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update sorting if defined.
|
|
||||||
this.updateCustomSorting<T>(finalOptions, attr, mgr)
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateCustomSorting<T extends Doc>(
|
|
||||||
finalOptions: FindOptions<T>,
|
|
||||||
attr: AnyAttribute,
|
|
||||||
mgr: StatusManager
|
|
||||||
): void {
|
|
||||||
const attrSort = finalOptions.sort?.[attr.name]
|
|
||||||
if (attrSort !== undefined && typeof attrSort !== 'object') {
|
|
||||||
// Fill custom sorting.
|
|
||||||
const statuses = mgr.statuses.filter((it) => it.ofAttribute === attr._id)
|
|
||||||
statuses.sort((a, b) => {
|
|
||||||
let ret = 0
|
|
||||||
if (a.category !== undefined && b.category !== undefined) {
|
|
||||||
ret = (a.$lookup?.category?.order ?? 0) - (b.$lookup?.category?.order ?? 0)
|
|
||||||
}
|
|
||||||
if (ret === 0) {
|
|
||||||
if (a.name.toLowerCase().trim() === b.name.toLowerCase().trim()) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
ret = a.rank.localeCompare(b.rank)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
})
|
|
||||||
if (finalOptions.sort === undefined) {
|
|
||||||
finalOptions.sort = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rules: SortingRules<any> = {
|
|
||||||
order: attrSort,
|
|
||||||
cases: statuses.map((it, idx) => ({ query: it._id, index: idx })),
|
|
||||||
default: statuses.length + 1
|
|
||||||
}
|
|
||||||
;(finalOptions.sort as any)[attr.name] = rules
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async tx (tx: Tx): Promise<TxResult> {
|
|
||||||
return await this.provideTx(tx)
|
|
||||||
}
|
|
||||||
}
|
|
@ -45,8 +45,6 @@ import { onDestroy } from 'svelte'
|
|||||||
import { KeyedAttribute } from '..'
|
import { KeyedAttribute } from '..'
|
||||||
import { PresentationPipeline, PresentationPipelineImpl } from './pipeline'
|
import { PresentationPipeline, PresentationPipelineImpl } from './pipeline'
|
||||||
import plugin from './plugin'
|
import plugin from './plugin'
|
||||||
import { StatusMiddleware, statusStore } from './status'
|
|
||||||
export { statusStore }
|
|
||||||
|
|
||||||
let liveQuery: LQ
|
let liveQuery: LQ
|
||||||
let client: TxOperations
|
let client: TxOperations
|
||||||
@ -114,7 +112,10 @@ export async function setClient (_client: Client): Promise<void> {
|
|||||||
if (pipeline !== undefined) {
|
if (pipeline !== undefined) {
|
||||||
await pipeline.close()
|
await pipeline.close()
|
||||||
}
|
}
|
||||||
pipeline = PresentationPipelineImpl.create(_client, [StatusMiddleware.create])
|
const factories = await _client.findAll(plugin.class.PresentationMiddlewareFactory, {})
|
||||||
|
const promises = factories.map(async (it) => await getResource(it.createPresentationMiddleware))
|
||||||
|
const creators = await Promise.all(promises)
|
||||||
|
pipeline = PresentationPipelineImpl.create(_client, creators)
|
||||||
|
|
||||||
const needRefresh = liveQuery !== undefined
|
const needRefresh = liveQuery !== undefined
|
||||||
liveQuery = new LQ(pipeline)
|
liveQuery = new LQ(pipeline)
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { Item, Kanban as KanbanUI } from '@hcengineering/kanban'
|
import { Item, Kanban as KanbanUI } from '@hcengineering/kanban'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient, statusStore, ActionContext } from '@hcengineering/presentation'
|
import { createQuery, getClient, ActionContext } from '@hcengineering/presentation'
|
||||||
import { Kanban, SpaceWithStates, Task, TaskGrouping, TaskOrdering } from '@hcengineering/task'
|
import { Kanban, SpaceWithStates, Task, TaskGrouping, TaskOrdering } from '@hcengineering/task'
|
||||||
import {
|
import {
|
||||||
ColorDefinition,
|
ColorDefinition,
|
||||||
@ -173,7 +173,7 @@
|
|||||||
viewOptions: ViewOptions,
|
viewOptions: ViewOptions,
|
||||||
viewOptionsModel: ViewOptionModel[] | undefined
|
viewOptionsModel: ViewOptionModel[] | undefined
|
||||||
) {
|
) {
|
||||||
categories = await getCategories(client, _class, docs, groupByKey, $statusStore, viewlet.descriptor)
|
categories = await getCategories(client, _class, docs, groupByKey, viewlet.descriptor)
|
||||||
for (const viewOption of viewOptionsModel ?? []) {
|
for (const viewOption of viewOptionsModel ?? []) {
|
||||||
if (viewOption.actionTarget !== 'category') continue
|
if (viewOption.actionTarget !== 'category') continue
|
||||||
const categoryFunc = viewOption as CategoryOption
|
const categoryFunc = viewOption as CategoryOption
|
||||||
@ -191,7 +191,6 @@
|
|||||||
groupByKey,
|
groupByKey,
|
||||||
update,
|
update,
|
||||||
queryId,
|
queryId,
|
||||||
$statusStore,
|
|
||||||
viewlet.descriptor
|
viewlet.descriptor
|
||||||
)
|
)
|
||||||
if (res !== undefined) {
|
if (res !== undefined) {
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, StatusValue } from '@hcengineering/core'
|
import { Ref, Status, StatusValue } from '@hcengineering/core'
|
||||||
import { statusStore } from '@hcengineering/presentation'
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import type { DoneState } from '@hcengineering/task'
|
import type { DoneState } from '@hcengineering/task'
|
||||||
import DoneStatePresenter from './DoneStatePresenter.svelte'
|
import DoneStatePresenter from './DoneStatePresenter.svelte'
|
||||||
import DoneStateEditor from './DoneStateEditor.svelte'
|
import DoneStateEditor from './DoneStateEditor.svelte'
|
||||||
@ -23,10 +23,11 @@
|
|||||||
export let value: Ref<DoneState> | StatusValue
|
export let value: Ref<DoneState> | StatusValue
|
||||||
export let showTitle: boolean = true
|
export let showTitle: boolean = true
|
||||||
export let onChange: ((value: Ref<DoneState>) => void) | undefined = undefined
|
export let onChange: ((value: Ref<DoneState>) => void) | undefined = undefined
|
||||||
|
|
||||||
|
$: state = $statusStore.get(typeof value === 'string' ? value : (value?.values?.[0]?._id as Ref<Status>))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
{@const state = $statusStore.get(typeof value === 'string' ? value : value.values[0]._id)}
|
|
||||||
{#if onChange !== undefined && state !== undefined}
|
{#if onChange !== undefined && state !== undefined}
|
||||||
<DoneStateEditor value={state._id} space={state.space} {onChange} kind="link" size="medium" />
|
<DoneStateEditor value={state._id} space={state.space} {onChange} kind="link" size="medium" />
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -27,8 +27,6 @@
|
|||||||
export let value: State | undefined
|
export let value: State | undefined
|
||||||
export let shouldShowAvatar = true
|
export let shouldShowAvatar = true
|
||||||
export let inline: boolean = false
|
export let inline: boolean = false
|
||||||
export let colorInherit: boolean = false
|
|
||||||
export let accent: boolean = false
|
|
||||||
export let disabled: boolean = false
|
export let disabled: boolean = false
|
||||||
export let oneLine: boolean = false
|
export let oneLine: boolean = false
|
||||||
|
|
||||||
|
@ -14,25 +14,23 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, StatusValue } from '@hcengineering/core'
|
import { Ref, Status, StatusValue } from '@hcengineering/core'
|
||||||
import { statusStore } from '@hcengineering/presentation'
|
|
||||||
import type { ButtonSize } from '@hcengineering/ui'
|
import type { ButtonSize } from '@hcengineering/ui'
|
||||||
import { State } from '@hcengineering/task'
|
import { State } from '@hcengineering/task'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import StateEditor from './StateEditor.svelte'
|
import StateEditor from './StateEditor.svelte'
|
||||||
import StatePresenter from './StatePresenter.svelte'
|
import StatePresenter from './StatePresenter.svelte'
|
||||||
|
|
||||||
export let value: Ref<State> | StatusValue
|
export let value: Ref<State> | StatusValue
|
||||||
export let onChange: ((value: Ref<State>) => void) | undefined = undefined
|
export let onChange: ((value: Ref<State>) => void) | undefined = undefined
|
||||||
export let colorInherit: boolean = false
|
|
||||||
export let accent: boolean = false
|
|
||||||
export let size: ButtonSize = 'medium'
|
export let size: ButtonSize = 'medium'
|
||||||
|
$: state = $statusStore.get(typeof value === 'string' ? value : (value?.values?.[0]?._id as Ref<Status>))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
{@const state = $statusStore.get(typeof value === 'string' ? value : value.values?.[0]?._id)}
|
|
||||||
{#if onChange !== undefined && state !== undefined}
|
{#if onChange !== undefined && state !== undefined}
|
||||||
<StateEditor value={state._id} space={state.space} {onChange} kind="link" {size} />
|
<StateEditor value={state._id} space={state.space} {onChange} kind="link" {size} />
|
||||||
{:else}
|
{:else}
|
||||||
<StatePresenter value={state} {colorInherit} {accent} on:accent-color />
|
<StatePresenter value={state} on:accent-color />
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -15,9 +15,10 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref } from '@hcengineering/core'
|
import { Ref } from '@hcengineering/core'
|
||||||
import { BreadcrumbsElement, statusStore } from '@hcengineering/presentation'
|
import { BreadcrumbsElement } from '@hcengineering/presentation'
|
||||||
import task, { SpaceWithStates, State } from '@hcengineering/task'
|
import task, { SpaceWithStates, State } from '@hcengineering/task'
|
||||||
import { ScrollerBar, getColorNumberByText, getPlatformColor, themeStore } from '@hcengineering/ui'
|
import { ScrollerBar, getColorNumberByText, getPlatformColor, themeStore } from '@hcengineering/ui'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import type { StatesBarPosition } from '../..'
|
import type { StatesBarPosition } from '../..'
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
"@hcengineering/chunter-resources": "^0.6.0",
|
"@hcengineering/chunter-resources": "^0.6.0",
|
||||||
"@hcengineering/workbench-resources": "^0.6.1",
|
"@hcengineering/workbench-resources": "^0.6.1",
|
||||||
"@hcengineering/activity-resources": "^0.6.1",
|
"@hcengineering/activity-resources": "^0.6.1",
|
||||||
"@hcengineering/activity": "^0.6.0"
|
"@hcengineering/activity": "^0.6.0",
|
||||||
|
"@hcengineering/query": "^0.6.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
238
plugins/tracker-resources/src/component.ts
Normal file
238
plugins/tracker-resources/src/component.ts
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2023 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import {
|
||||||
|
AggregateValue,
|
||||||
|
AggregateValueData,
|
||||||
|
AnyAttribute,
|
||||||
|
Attribute,
|
||||||
|
Class,
|
||||||
|
Client,
|
||||||
|
Doc,
|
||||||
|
DocumentQuery,
|
||||||
|
Hierarchy,
|
||||||
|
Ref,
|
||||||
|
SortingOrder,
|
||||||
|
Space,
|
||||||
|
Tx,
|
||||||
|
WithLookup,
|
||||||
|
matchQuery
|
||||||
|
} from '@hcengineering/core'
|
||||||
|
import { LiveQuery } from '@hcengineering/query'
|
||||||
|
import tracker, { Component, ComponentManager } from '@hcengineering/tracker'
|
||||||
|
import { AggregationManager, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateLookup (resultDoc: WithLookup<Doc>, attr: Attribute<Doc>): Promise<void> {
|
||||||
|
const value = (resultDoc as any)[attr.name]
|
||||||
|
const doc = (await this.getManager()).getIdMap().get(value)
|
||||||
|
if (doc !== undefined) {
|
||||||
|
;(resultDoc.$lookup as any)[attr.name] = doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export const grouppingComponentManager: GrouppingManager = {
|
||||||
|
groupByCategories: groupByComponentCategories,
|
||||||
|
groupValues: groupComponentValues,
|
||||||
|
groupValuesWithEmpty: groupComponentValuesWithEmpty,
|
||||||
|
hasValue: hasComponentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function groupByComponentCategories (categories: any[]): AggregateValue[] {
|
||||||
|
const mgr = get(componentStore)
|
||||||
|
|
||||||
|
const existingCategories: AggregateValue[] = [new AggregateValue(undefined, [])]
|
||||||
|
const componentMap = new Map<string, AggregateValue>()
|
||||||
|
|
||||||
|
const usedSpaces = new Set<Ref<Space>>()
|
||||||
|
const componentsList: Array<WithLookup<Component>> = []
|
||||||
|
for (const v of categories) {
|
||||||
|
const component = mgr.getIdMap().get(v)
|
||||||
|
if (component !== undefined) {
|
||||||
|
componentsList.push(component)
|
||||||
|
usedSpaces.add(component.space)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const component of componentsList) {
|
||||||
|
if (component !== undefined) {
|
||||||
|
let fst = componentMap.get(component.label.toLowerCase().trim())
|
||||||
|
if (fst === undefined) {
|
||||||
|
const components = mgr
|
||||||
|
.getDocs()
|
||||||
|
.filter(
|
||||||
|
(it) =>
|
||||||
|
it.label.toLowerCase().trim() === component.label.toLowerCase().trim() &&
|
||||||
|
(categories.includes(it._id) || usedSpaces.has(it.space))
|
||||||
|
)
|
||||||
|
.sort((a, b) => a.label.localeCompare(b.label))
|
||||||
|
.map((it) => new AggregateValueData(it.label, it._id, it.space))
|
||||||
|
fst = new AggregateValue(component.label, components)
|
||||||
|
componentMap.set(component.label.toLowerCase().trim(), fst)
|
||||||
|
existingCategories.push(fst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return existingCategories
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function groupComponentValues (val: Doc[], targets: Set<any>): Doc[] {
|
||||||
|
const values = val
|
||||||
|
const result: Doc[] = []
|
||||||
|
const unique = [...new Set(val.map((c) => (c as Component).label.trim().toLocaleLowerCase()))]
|
||||||
|
unique.forEach((label, i) => {
|
||||||
|
let exists = false
|
||||||
|
values.forEach((c) => {
|
||||||
|
if ((c as Component).label.trim().toLocaleLowerCase() === label) {
|
||||||
|
if (!exists) {
|
||||||
|
result[i] = c
|
||||||
|
exists = targets.has(c?._id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function hasComponentValue (value: Doc | undefined | null, values: any[]): boolean {
|
||||||
|
const mgr = get(componentStore)
|
||||||
|
const componentSet = new Set(
|
||||||
|
mgr
|
||||||
|
.filter((it) => it.label.trim().toLocaleLowerCase() === (value as Component)?.label?.trim()?.toLocaleLowerCase())
|
||||||
|
.map((it) => it._id)
|
||||||
|
)
|
||||||
|
return values.some((it) => componentSet.has(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function groupComponentValuesWithEmpty (
|
||||||
|
hierarchy: Hierarchy,
|
||||||
|
_class: Ref<Class<Doc>>,
|
||||||
|
key: string,
|
||||||
|
query: DocumentQuery<Doc> | undefined
|
||||||
|
): Array<Ref<Doc>> {
|
||||||
|
const mgr = get(componentStore)
|
||||||
|
// We do not need extensions for all status categories.
|
||||||
|
let componentsList = 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
|
||||||
|
}
|
||||||
|
componentsList = matchQuery<Doc>(componentsList, resQuery, _class, hierarchy) as unknown as Array<
|
||||||
|
WithLookup<Component>
|
||||||
|
>
|
||||||
|
}
|
||||||
|
return componentsList.map((it) => it._id)
|
||||||
|
}
|
@ -14,14 +14,14 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { TxUpdateDoc } from '@hcengineering/core'
|
import { TxUpdateDoc } from '@hcengineering/core'
|
||||||
import { statusStore } from '@hcengineering/presentation'
|
|
||||||
import { Issue } from '@hcengineering/tracker'
|
import { Issue } from '@hcengineering/tracker'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import IssueStatusIcon from '../issues/IssueStatusIcon.svelte'
|
import IssueStatusIcon from '../issues/IssueStatusIcon.svelte'
|
||||||
|
|
||||||
export let tx: TxUpdateDoc<Issue>
|
export let tx: TxUpdateDoc<Issue>
|
||||||
$: value = tx.operations.status
|
$: value = tx.operations.status
|
||||||
|
|
||||||
$: status = value && $statusStore.byId.get(value)
|
$: status = value && $statusStore.getIdMap().get(value)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
|
@ -19,8 +19,9 @@
|
|||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
import { DocNavLink } from '@hcengineering/view-resources'
|
import { DocNavLink } from '@hcengineering/view-resources'
|
||||||
|
import { translate } from '@hcengineering/platform'
|
||||||
|
|
||||||
export let value: WithLookup<Component>
|
export let value: WithLookup<Component> | undefined
|
||||||
export let shouldShowAvatar = true
|
export let shouldShowAvatar = true
|
||||||
export let onClick: (() => void) | undefined = undefined
|
export let onClick: (() => void) | undefined = undefined
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
@ -28,24 +29,32 @@
|
|||||||
export let accent: boolean = false
|
export let accent: boolean = false
|
||||||
export let noUnderline = false
|
export let noUnderline = false
|
||||||
export let kind: 'list' | undefined = undefined
|
export let kind: 'list' | undefined = undefined
|
||||||
|
|
||||||
|
let label: string
|
||||||
|
|
||||||
|
$: if (value !== undefined) {
|
||||||
|
label = value.label
|
||||||
|
} else {
|
||||||
|
translate(tracker.string.NoComponent, {})
|
||||||
|
.then((r) => {
|
||||||
|
label = r
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
$: disabled = disabled || value === undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
<DocNavLink object={value} {onClick} {disabled} {noUnderline} {inline} {accent} component={view.component.EditDoc}>
|
||||||
<DocNavLink object={value} {onClick} {disabled} {noUnderline} {inline} {accent} component={view.component.EditDoc}>
|
|
||||||
<span class="flex-presenter" class:inline-presenter={inline} class:list={kind === 'list'}>
|
<span class="flex-presenter" class:inline-presenter={inline} class:list={kind === 'list'}>
|
||||||
{#if !inline && shouldShowAvatar}
|
{#if !inline && shouldShowAvatar}
|
||||||
<div class="icon" use:tooltip={{ label: tracker.string.Component }}>
|
<div class="icon" use:tooltip={{ label: tracker.string.Component }}>
|
||||||
<Icon icon={tracker.icon.Component} size={'small'} />
|
<Icon icon={tracker.icon.Component} size={'small'} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<span
|
<span title={label} class="label nowrap" class:no-underline={disabled || noUnderline} class:fs-bold={accent}>
|
||||||
title={value.label}
|
{label}
|
||||||
class="label nowrap"
|
|
||||||
class:no-underline={disabled || noUnderline}
|
|
||||||
class:fs-bold={accent}
|
|
||||||
>
|
|
||||||
{value.label}
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</DocNavLink>
|
</DocNavLink>
|
||||||
{/if}
|
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { AggregateValue, Ref } from '@hcengineering/core'
|
||||||
|
import ComponentPresenter from './ComponentPresenter.svelte'
|
||||||
|
import { Component } from '@hcengineering/tracker'
|
||||||
|
|
||||||
|
import { componentStore } from '../../component'
|
||||||
|
|
||||||
|
export let value: Ref<Component> | AggregateValue | undefined
|
||||||
|
export let kind: 'list' | undefined = undefined
|
||||||
|
|
||||||
|
$: componentValue = $componentStore.get(
|
||||||
|
typeof value === 'string' ? value : (value?.values?.[0]?._id as Ref<Component>)
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ComponentPresenter value={componentValue} {kind} on:accent-color />
|
@ -3,8 +3,8 @@
|
|||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { Issue, IssueStatus } from '@hcengineering/tracker'
|
import { Issue, IssueStatus } from '@hcengineering/tracker'
|
||||||
import { Label, ticker, Row } from '@hcengineering/ui'
|
import { Label, ticker, Row } from '@hcengineering/ui'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import { statusStore } from '@hcengineering/presentation'
|
|
||||||
import Duration from './Duration.svelte'
|
import Duration from './Duration.svelte'
|
||||||
import StatusPresenter from './StatusPresenter.svelte'
|
import StatusPresenter from './StatusPresenter.svelte'
|
||||||
|
|
||||||
@ -84,7 +84,7 @@
|
|||||||
displaySt = result
|
displaySt = result
|
||||||
}
|
}
|
||||||
|
|
||||||
$: updateStatus(txes, $statusStore.byId, $ticker)
|
$: updateStatus(txes, $statusStore.getIdMap(), $ticker)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
|
@ -14,9 +14,10 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { StatusCategory, WithLookup } from '@hcengineering/core'
|
import core, { StatusCategory, WithLookup } from '@hcengineering/core'
|
||||||
import { getClient, statusStore } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { IssueStatus } from '@hcengineering/tracker'
|
import { IssueStatus } from '@hcengineering/tracker'
|
||||||
import { IconSize } from '@hcengineering/ui'
|
import { IconSize } from '@hcengineering/ui'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import StatusIcon from '../icons/StatusIcon.svelte'
|
import StatusIcon from '../icons/StatusIcon.svelte'
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
import { Item, Kanban } from '@hcengineering/kanban'
|
import { Item, Kanban } from '@hcengineering/kanban'
|
||||||
import notification from '@hcengineering/notification'
|
import notification from '@hcengineering/notification'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient, statusStore, ActionContext } from '@hcengineering/presentation'
|
import { createQuery, getClient, ActionContext } from '@hcengineering/presentation'
|
||||||
import tags from '@hcengineering/tags'
|
import tags from '@hcengineering/tags'
|
||||||
import { Issue, IssuesGrouping, IssuesOrdering, Project } from '@hcengineering/tracker'
|
import { Issue, IssuesGrouping, IssuesOrdering, Project } from '@hcengineering/tracker'
|
||||||
import {
|
import {
|
||||||
@ -197,7 +197,7 @@
|
|||||||
viewOptions: ViewOptions,
|
viewOptions: ViewOptions,
|
||||||
viewOptionsModel: ViewOptionModel[] | undefined
|
viewOptionsModel: ViewOptionModel[] | undefined
|
||||||
) {
|
) {
|
||||||
categories = await getCategories(client, _class, docs, groupByKey, $statusStore, viewlet.descriptor)
|
categories = await getCategories(client, _class, docs, groupByKey, viewlet.descriptor)
|
||||||
for (const viewOption of viewOptionsModel ?? []) {
|
for (const viewOption of viewOptionsModel ?? []) {
|
||||||
if (viewOption.actionTarget !== 'category') continue
|
if (viewOption.actionTarget !== 'category') continue
|
||||||
const categoryFunc = viewOption as CategoryOption
|
const categoryFunc = viewOption as CategoryOption
|
||||||
@ -214,7 +214,6 @@
|
|||||||
groupByKey,
|
groupByKey,
|
||||||
update,
|
update,
|
||||||
queryId,
|
queryId,
|
||||||
$statusStore,
|
|
||||||
viewlet.descriptor
|
viewlet.descriptor
|
||||||
)
|
)
|
||||||
if (res !== undefined) {
|
if (res !== undefined) {
|
||||||
|
@ -15,10 +15,11 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DocumentUpdate, Ref } from '@hcengineering/core'
|
import { DocumentUpdate, Ref } from '@hcengineering/core'
|
||||||
import { SpaceSelect, createQuery, getClient, statusStore } from '@hcengineering/presentation'
|
import { SpaceSelect, createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { Component, Issue, Project } from '@hcengineering/tracker'
|
import { Component, Issue, Project } from '@hcengineering/tracker'
|
||||||
import ui, { Button, Label, Spinner } from '@hcengineering/ui'
|
import ui, { Button, Label, Spinner } from '@hcengineering/ui'
|
||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import { collectIssues, findTargetStatus, moveIssueToSpace } from '../../utils'
|
import { collectIssues, findTargetStatus, moveIssueToSpace } from '../../utils'
|
||||||
|
@ -14,10 +14,19 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AttachedData, Ref, StatusManager, WithLookup } from '@hcengineering/core'
|
import { AttachedData, Ref, StatusManager, WithLookup } from '@hcengineering/core'
|
||||||
import { getClient, statusStore } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { Issue, IssueDraft, IssueStatus, Project } from '@hcengineering/tracker'
|
import { Issue, IssueDraft, IssueStatus, Project } from '@hcengineering/tracker'
|
||||||
import type { ButtonKind, ButtonSize, IconSize } from '@hcengineering/ui'
|
import {
|
||||||
import { Button, SelectPopup, TooltipAlignment, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
ButtonKind,
|
||||||
|
ButtonSize,
|
||||||
|
IconSize,
|
||||||
|
Button,
|
||||||
|
SelectPopup,
|
||||||
|
TooltipAlignment,
|
||||||
|
eventToHTMLElement,
|
||||||
|
showPopup
|
||||||
|
} from '@hcengineering/ui'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import IssueStatusIcon from './IssueStatusIcon.svelte'
|
import IssueStatusIcon from './IssueStatusIcon.svelte'
|
||||||
|
@ -15,8 +15,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { IssueStatus } from '@hcengineering/tracker'
|
import { IssueStatus } from '@hcengineering/tracker'
|
||||||
import IssueStatusIcon from './IssueStatusIcon.svelte'
|
import IssueStatusIcon from './IssueStatusIcon.svelte'
|
||||||
import { createQuery, statusStore } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import core, { IdMap, Ref, StatusCategory, toIdMap } from '@hcengineering/core'
|
import core, { IdMap, Ref, StatusCategory, toIdMap } from '@hcengineering/core'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
|
|
||||||
export let value: Ref<IssueStatus>[]
|
export let value: Ref<IssueStatus>[]
|
||||||
|
|
||||||
@ -41,7 +42,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$: statuses = sort(value.map((p) => $statusStore.byId.get(p)) as IssueStatus[], categories)
|
$: statuses = sort(value.map((p) => $statusStore.getIdMap().get(p)) as IssueStatus[], categories)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-presenter flex-gap-1-5">
|
<div class="flex-presenter flex-gap-1-5">
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, Status, StatusValue } from '@hcengineering/core'
|
import { Ref, Status, StatusValue } from '@hcengineering/core'
|
||||||
import { statusStore } from '@hcengineering/presentation'
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import StatusPresenter from './StatusPresenter.svelte'
|
import StatusPresenter from './StatusPresenter.svelte'
|
||||||
|
|
||||||
export let value: Ref<Status> | StatusValue | undefined
|
export let value: Ref<Status> | StatusValue | undefined
|
||||||
@ -22,15 +22,10 @@
|
|||||||
export let kind: 'list-header' | undefined = undefined
|
export let kind: 'list-header' | undefined = undefined
|
||||||
export let colorInherit: boolean = false
|
export let colorInherit: boolean = false
|
||||||
export let accent: boolean = false
|
export let accent: boolean = false
|
||||||
|
|
||||||
|
$: statusValue = $statusStore.get(typeof value === 'string' ? value : (value?.values?.[0]?._id as Ref<Status>))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
<StatusPresenter
|
<StatusPresenter value={statusValue} {size} {kind} {colorInherit} {accent} on:accent-color />
|
||||||
value={$statusStore.get(typeof value === 'string' ? value : value.values?.[0]?._id)}
|
|
||||||
{size}
|
|
||||||
{kind}
|
|
||||||
{colorInherit}
|
|
||||||
{accent}
|
|
||||||
on:accent-color
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { SortingOrder, WithLookup } from '@hcengineering/core'
|
import core, { SortingOrder, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery, statusStore } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { Issue, IssueStatus } from '@hcengineering/tracker'
|
import { Issue, IssueStatus } from '@hcengineering/tracker'
|
||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
@ -29,7 +29,7 @@
|
|||||||
themeStore,
|
themeStore,
|
||||||
tooltip
|
tooltip
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { ListSelectionProvider } from '@hcengineering/view-resources'
|
import { ListSelectionProvider, statusStore } from '@hcengineering/view-resources'
|
||||||
import { getIssueId, issueLinkFragmentProvider } from '../../../issues'
|
import { getIssueId, issueLinkFragmentProvider } from '../../../issues'
|
||||||
import tracker from '../../../plugin'
|
import tracker from '../../../plugin'
|
||||||
import IssueStatusIcon from '../IssueStatusIcon.svelte'
|
import IssueStatusIcon from '../IssueStatusIcon.svelte'
|
||||||
@ -118,7 +118,7 @@
|
|||||||
subIssuesQeury.unsubscribe()
|
subIssuesQeury.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
$: parentStatus = parentIssue ? $statusStore.byId.get(parentIssue.status) : undefined
|
$: parentStatus = parentIssue ? $statusStore.getIdMap().get(parentIssue.status) : undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if parentIssue}
|
{#if parentIssue}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, SortingOrder, WithLookup } from '@hcengineering/core'
|
import { Ref, SortingOrder, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery, statusStore } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { Issue, Project } from '@hcengineering/tracker'
|
import { Issue, Project } from '@hcengineering/tracker'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -26,6 +26,7 @@
|
|||||||
showPanel,
|
showPanel,
|
||||||
showPopup
|
showPopup
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import { getIssueId } from '../../../issues'
|
import { getIssueId } from '../../../issues'
|
||||||
import tracker from '../../../plugin'
|
import tracker from '../../../plugin'
|
||||||
import { subIssueListProvider } from '../../../utils'
|
import { subIssueListProvider } from '../../../utils'
|
||||||
@ -77,7 +78,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: if (subIssues) {
|
$: if (subIssues) {
|
||||||
const doneStatuses = $statusStore.statuses
|
const doneStatuses = $statusStore
|
||||||
|
.getDocs()
|
||||||
.filter(
|
.filter(
|
||||||
(s) =>
|
(s) =>
|
||||||
s.category === tracker.issueStatusCategory.Completed || s.category === tracker.issueStatusCategory.Canceled
|
s.category === tracker.issueStatusCategory.Completed || s.category === tracker.issueStatusCategory.Canceled
|
||||||
|
@ -14,9 +14,10 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, Status } from '@hcengineering/core'
|
import { Ref, Status } from '@hcengineering/core'
|
||||||
import { getClient, statusStore } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { Issue, Project } from '@hcengineering/tracker'
|
import { Issue, Project } from '@hcengineering/tracker'
|
||||||
import { Button, Label } from '@hcengineering/ui'
|
import { Button, Label } from '@hcengineering/ui'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import tracker from '../../../plugin'
|
import tracker from '../../../plugin'
|
||||||
import { findTargetStatus } from '../../../utils'
|
import { findTargetStatus } from '../../../utils'
|
||||||
import StatusRefPresenter from '../StatusRefPresenter.svelte'
|
import StatusRefPresenter from '../StatusRefPresenter.svelte'
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DocumentUpdate, Ref } from '@hcengineering/core'
|
import { DocumentUpdate, Ref } from '@hcengineering/core'
|
||||||
import { statusStore } from '@hcengineering/presentation'
|
|
||||||
import { Issue, Project } from '@hcengineering/tracker'
|
import { Issue, Project } from '@hcengineering/tracker'
|
||||||
import { Label } from '@hcengineering/ui'
|
import { Label } from '@hcengineering/ui'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import tracker from '../../../plugin'
|
import tracker from '../../../plugin'
|
||||||
import { findTargetStatus, issueToAttachedData } from '../../../utils'
|
import { findTargetStatus, issueToAttachedData } from '../../../utils'
|
||||||
import StatusEditor from '../StatusEditor.svelte'
|
import StatusEditor from '../StatusEditor.svelte'
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Issue, Project } from '@hcengineering/tracker'
|
import { Issue, Project } from '@hcengineering/tracker'
|
||||||
import { statusStore } from '@hcengineering/presentation'
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
|
|
||||||
import IssueStatusIcon from '../IssueStatusIcon.svelte'
|
import IssueStatusIcon from '../IssueStatusIcon.svelte'
|
||||||
import { getIssueId } from '../../../issues'
|
import { getIssueId } from '../../../issues'
|
||||||
@ -23,7 +23,7 @@
|
|||||||
export let issue: Issue
|
export let issue: Issue
|
||||||
export let size: 'small' | 'medium' | 'large' = 'small'
|
export let size: 'small' | 'medium' | 'large' = 'small'
|
||||||
|
|
||||||
$: status = $statusStore.byId.get(issue.status)
|
$: status = $statusStore.getIdMap().get(issue.status)
|
||||||
$: huge = size === 'medium' || size === 'large'
|
$: huge = size === 'medium' || size === 'large'
|
||||||
$: text = project ? `${getIssueId(project, issue)} ${issue.title}` : issue.title
|
$: text = project ? `${getIssueId(project, issue)} ${issue.title}` : issue.title
|
||||||
</script>
|
</script>
|
||||||
|
@ -26,9 +26,9 @@
|
|||||||
showPanel,
|
showPanel,
|
||||||
showPopup
|
showPopup
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import tracker from '../../../plugin'
|
import tracker from '../../../plugin'
|
||||||
import { subIssueListProvider } from '../../../utils'
|
import { subIssueListProvider } from '../../../utils'
|
||||||
import { statusStore } from '@hcengineering/presentation'
|
|
||||||
import RelatedIssuePresenter from './RelatedIssuePresenter.svelte'
|
import RelatedIssuePresenter from './RelatedIssuePresenter.svelte'
|
||||||
|
|
||||||
export let object: WithLookup<Doc & { related: number }> | undefined
|
export let object: WithLookup<Doc & { related: number }> | undefined
|
||||||
@ -69,7 +69,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: if (subIssues) {
|
$: if (subIssues) {
|
||||||
const doneStatuses = $statusStore.statuses
|
const doneStatuses = $statusStore
|
||||||
|
.getDocs()
|
||||||
.filter((s) => s.category === tracker.issueStatusCategory.Completed)
|
.filter((s) => s.category === tracker.issueStatusCategory.Completed)
|
||||||
.map((p) => p._id)
|
.map((p) => p._id)
|
||||||
countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length
|
countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import { Issue } from '@hcengineering/tracker'
|
import { Issue } from '@hcengineering/tracker'
|
||||||
import { floorFractionDigits, Label } from '@hcengineering/ui'
|
import { floorFractionDigits, Label } from '@hcengineering/ui'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import { statusStore } from '@hcengineering/presentation'
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import EstimationProgressCircle from '../issues/timereport/EstimationProgressCircle.svelte'
|
import EstimationProgressCircle from '../issues/timereport/EstimationProgressCircle.svelte'
|
||||||
import TimePresenter from '../issues/timereport/TimePresenter.svelte'
|
import TimePresenter from '../issues/timereport/TimePresenter.svelte'
|
||||||
import { FixedColumn } from '@hcengineering/view-resources'
|
import { FixedColumn } from '@hcengineering/view-resources'
|
||||||
@ -30,13 +30,13 @@
|
|||||||
$: noParents = docs?.filter((it) => !ids.has(it.attachedTo as Ref<Issue>))
|
$: noParents = docs?.filter((it) => !ids.has(it.attachedTo as Ref<Issue>))
|
||||||
|
|
||||||
$: rootNoBacklogIssues = noParents?.filter(
|
$: rootNoBacklogIssues = noParents?.filter(
|
||||||
(it) => $statusStore.byId.get(it.status)?.category !== tracker.issueStatusCategory.Backlog
|
(it) => $statusStore.getIdMap().get(it.status)?.category !== tracker.issueStatusCategory.Backlog
|
||||||
)
|
)
|
||||||
|
|
||||||
$: totalEstimation = floorFractionDigits(
|
$: totalEstimation = floorFractionDigits(
|
||||||
(rootNoBacklogIssues ?? [{ estimation: 0, childInfo: [] } as unknown as Issue])
|
(rootNoBacklogIssues ?? [{ estimation: 0, childInfo: [] } as unknown as Issue])
|
||||||
.map((it) => {
|
.map((it) => {
|
||||||
const cat = $statusStore.byId.get(it.status)?.category
|
const cat = $statusStore.getIdMap().get(it.status)?.category
|
||||||
|
|
||||||
let retEst = it.estimation
|
let retEst = it.estimation
|
||||||
if (it.childInfo?.length > 0) {
|
if (it.childInfo?.length > 0) {
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
import { Ref } from '@hcengineering/core'
|
import { Ref } from '@hcengineering/core'
|
||||||
import { Issue, IssueStatus, Project } from '@hcengineering/tracker'
|
import { Issue, IssueStatus, Project } from '@hcengineering/tracker'
|
||||||
import { Button, Label, SelectPopup, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
import { Button, Label, SelectPopup, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||||
import presentation, { getClient, statusStore } from '@hcengineering/presentation'
|
import presentation, { getClient } from '@hcengineering/presentation'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import IssueStatusIcon from '../issues/IssueStatusIcon.svelte'
|
import IssueStatusIcon from '../issues/IssueStatusIcon.svelte'
|
||||||
import { StatusPresenter } from '@hcengineering/view-resources'
|
import { StatusPresenter, statusStore } from '@hcengineering/view-resources'
|
||||||
|
|
||||||
export let projectId: Ref<Project>
|
export let projectId: Ref<Project>
|
||||||
export let issues: Issue[]
|
export let issues: Issue[]
|
||||||
@ -17,10 +17,10 @@
|
|||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
let newStatus: IssueStatus =
|
let newStatus: IssueStatus =
|
||||||
$statusStore.statuses.find(
|
$statusStore
|
||||||
(s) => s._id !== status._id && s.category === status.category && s.space === projectId
|
.getDocs()
|
||||||
) ??
|
.find((s) => s._id !== status._id && s.category === status.category && s.space === projectId) ??
|
||||||
$statusStore.statuses.find((s) => s._id !== status._id && s.space === projectId) ??
|
$statusStore.getDocs().find((s) => s._id !== status._id && s.space === projectId) ??
|
||||||
status
|
status
|
||||||
|
|
||||||
async function remove () {
|
async function remove () {
|
||||||
@ -54,7 +54,7 @@
|
|||||||
SelectPopup,
|
SelectPopup,
|
||||||
{ value: statusesInfo, placeholder: tracker.string.SetStatus, searchable: true },
|
{ value: statusesInfo, placeholder: tracker.string.SetStatus, searchable: true },
|
||||||
eventToHTMLElement(event),
|
eventToHTMLElement(event),
|
||||||
(val) => (newStatus = $statusStore.byId.get(val) ?? newStatus)
|
(val) => (newStatus = $statusStore.getIdMap().get(val) ?? newStatus)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -28,13 +28,13 @@
|
|||||||
Scroller,
|
Scroller,
|
||||||
showPopup
|
showPopup
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { statusStore } from '@hcengineering/presentation'
|
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { flip } from 'svelte/animate'
|
import { flip } from 'svelte/animate'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import StatusEditor from './StatusEditor.svelte'
|
import StatusEditor from './StatusEditor.svelte'
|
||||||
import StatusPresenter from './StatusPresenter.svelte'
|
import StatusPresenter from './StatusPresenter.svelte'
|
||||||
import RemoveStatus from './RemoveStatus.svelte'
|
import RemoveStatus from './RemoveStatus.svelte'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
|
|
||||||
export let projectId: Ref<Project>
|
export let projectId: Ref<Project>
|
||||||
export let projectClass: Ref<Class<Project>>
|
export let projectClass: Ref<Class<Project>>
|
||||||
@ -88,7 +88,7 @@
|
|||||||
async function editStatus () {
|
async function editStatus () {
|
||||||
if (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 statusId = '_id' in editingStatus ? editingStatus._id : undefined
|
||||||
const status = statusId && $statusStore.byId.get(statusId)
|
const status = statusId && $statusStore.getIdMap().get(statusId)
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
return
|
return
|
||||||
@ -238,7 +238,7 @@
|
|||||||
|
|
||||||
$: projectQuery.query(projectClass, { _id: projectId }, (result) => ([project] = result), { limit: 1 })
|
$: projectQuery.query(projectClass, { _id: projectId }, (result) => ([project] = result), { limit: 1 })
|
||||||
$: updateStatusCategories()
|
$: updateStatusCategories()
|
||||||
$: projectStatuses = $statusStore.statuses.filter((status) => status.space === projectId)
|
$: projectStatuses = $statusStore.getDocs().filter((status) => status.space === projectId)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel isHeader={false} isAside={false} on:fullsize on:close={() => dispatch('close')}>
|
<Panel isHeader={false} isAside={false} on:fullsize on:close={() => dispatch('close')}>
|
||||||
|
@ -29,6 +29,7 @@ import { Issue, Project, Scrum, ScrumRecord, Milestone } from '@hcengineering/tr
|
|||||||
import { showPopup } from '@hcengineering/ui'
|
import { showPopup } from '@hcengineering/ui'
|
||||||
import ComponentEditor from './components/components/ComponentEditor.svelte'
|
import ComponentEditor from './components/components/ComponentEditor.svelte'
|
||||||
import ComponentPresenter from './components/components/ComponentPresenter.svelte'
|
import ComponentPresenter from './components/components/ComponentPresenter.svelte'
|
||||||
|
import ComponentRefPresenter from './components/components/ComponentRefPresenter.svelte'
|
||||||
import Components from './components/components/Components.svelte'
|
import Components from './components/components/Components.svelte'
|
||||||
import ComponentTitlePresenter from './components/components/ComponentTitlePresenter.svelte'
|
import ComponentTitlePresenter from './components/components/ComponentTitlePresenter.svelte'
|
||||||
import EditComponent from './components/components/EditComponent.svelte'
|
import EditComponent from './components/components/EditComponent.svelte'
|
||||||
@ -132,6 +133,7 @@ import ProjectSpacePresenter from './components/projects/ProjectSpacePresenter.s
|
|||||||
import IssueStatistics from './components/milestones/IssueStatistics.svelte'
|
import IssueStatistics from './components/milestones/IssueStatistics.svelte'
|
||||||
import MilestoneRefPresenter from './components/milestones/MilestoneRefPresenter.svelte'
|
import MilestoneRefPresenter from './components/milestones/MilestoneRefPresenter.svelte'
|
||||||
import MilestoneFilter from './components/milestones/MilestoneFilter.svelte'
|
import MilestoneFilter from './components/milestones/MilestoneFilter.svelte'
|
||||||
|
import { ComponentAggregationManager, grouppingComponentManager } from './component'
|
||||||
|
|
||||||
export { default as SubIssueList } from './components/issues/edit/SubIssueList.svelte'
|
export { default as SubIssueList } from './components/issues/edit/SubIssueList.svelte'
|
||||||
|
|
||||||
@ -385,6 +387,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
Components,
|
Components,
|
||||||
IssuePresenter,
|
IssuePresenter,
|
||||||
ComponentPresenter,
|
ComponentPresenter,
|
||||||
|
ComponentRefPresenter,
|
||||||
ComponentTitlePresenter,
|
ComponentTitlePresenter,
|
||||||
TitlePresenter,
|
TitlePresenter,
|
||||||
ModificationDatePresenter,
|
ModificationDatePresenter,
|
||||||
@ -473,5 +476,9 @@ export default async (): Promise<Resources> => ({
|
|||||||
},
|
},
|
||||||
resolver: {
|
resolver: {
|
||||||
Location: resolveLocation
|
Location: resolveLocation
|
||||||
|
},
|
||||||
|
aggregation: {
|
||||||
|
CreateComponentAggregationManager: ComponentAggregationManager.create,
|
||||||
|
GrouppingComponentManager: grouppingComponentManager
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -17,7 +17,15 @@ import type { Asset, IntlString, Metadata, Resource } from '@hcengineering/platf
|
|||||||
import { mergeIds } from '@hcengineering/platform'
|
import { mergeIds } from '@hcengineering/platform'
|
||||||
import { IssueDraft } from '@hcengineering/tracker'
|
import { IssueDraft } from '@hcengineering/tracker'
|
||||||
import { AnyComponent, Location } from '@hcengineering/ui'
|
import { AnyComponent, Location } from '@hcengineering/ui'
|
||||||
import { GetAllValuesFunc, SortFunc, Viewlet, ViewletDescriptor, ViewQueryAction } from '@hcengineering/view'
|
import {
|
||||||
|
CreateAggregationManagerFunc,
|
||||||
|
GetAllValuesFunc,
|
||||||
|
GrouppingManagerResource,
|
||||||
|
SortFunc,
|
||||||
|
Viewlet,
|
||||||
|
ViewletDescriptor,
|
||||||
|
ViewQueryAction
|
||||||
|
} from '@hcengineering/view'
|
||||||
import tracker, { trackerId } from '../../tracker/lib'
|
import tracker, { trackerId } from '../../tracker/lib'
|
||||||
|
|
||||||
export default mergeIds(trackerId, tracker, {
|
export default mergeIds(trackerId, tracker, {
|
||||||
@ -314,6 +322,7 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
IssuePresenter: '' as AnyComponent,
|
IssuePresenter: '' as AnyComponent,
|
||||||
ComponentTitlePresenter: '' as AnyComponent,
|
ComponentTitlePresenter: '' as AnyComponent,
|
||||||
ComponentPresenter: '' as AnyComponent,
|
ComponentPresenter: '' as AnyComponent,
|
||||||
|
ComponentRefPresenter: '' as AnyComponent,
|
||||||
TitlePresenter: '' as AnyComponent,
|
TitlePresenter: '' as AnyComponent,
|
||||||
ModificationDatePresenter: '' as AnyComponent,
|
ModificationDatePresenter: '' as AnyComponent,
|
||||||
PriorityPresenter: '' as AnyComponent,
|
PriorityPresenter: '' as AnyComponent,
|
||||||
@ -382,5 +391,9 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
GetAllPriority: '' as GetAllValuesFunc,
|
GetAllPriority: '' as GetAllValuesFunc,
|
||||||
GetAllComponents: '' as GetAllValuesFunc,
|
GetAllComponents: '' as GetAllValuesFunc,
|
||||||
GetAllMilestones: '' as GetAllValuesFunc
|
GetAllMilestones: '' as GetAllValuesFunc
|
||||||
|
},
|
||||||
|
aggregation: {
|
||||||
|
CreateComponentAggregationManager: '' as CreateAggregationManagerFunc,
|
||||||
|
GrouppingComponentManager: '' as GrouppingManagerResource
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -61,7 +61,7 @@ import {
|
|||||||
import { ViewletDescriptor } from '@hcengineering/view'
|
import { ViewletDescriptor } from '@hcengineering/view'
|
||||||
import { CategoryQuery, groupBy, ListSelectionProvider, SelectDirection } from '@hcengineering/view-resources'
|
import { CategoryQuery, groupBy, ListSelectionProvider, SelectDirection } from '@hcengineering/view-resources'
|
||||||
import tracker from './plugin'
|
import tracker from './plugin'
|
||||||
import { defaultMilestoneStatuses, defaultPriorities } from './types'
|
import { defaultPriorities, defaultMilestoneStatuses } from './types'
|
||||||
|
|
||||||
export * from './types'
|
export * from './types'
|
||||||
|
|
||||||
@ -296,7 +296,7 @@ export async function issueStatusSort (
|
|||||||
listIssueKanbanStatusOrder.indexOf(a.values[0].category as Ref<StatusCategory>) -
|
listIssueKanbanStatusOrder.indexOf(a.values[0].category as Ref<StatusCategory>) -
|
||||||
listIssueKanbanStatusOrder.indexOf(b.values[0].category as Ref<StatusCategory>)
|
listIssueKanbanStatusOrder.indexOf(b.values[0].category as Ref<StatusCategory>)
|
||||||
if (res === 0) {
|
if (res === 0) {
|
||||||
return a.values[0].rank.localeCompare(b.values[0].rank)
|
return a.values[0].getRank().localeCompare(b.values[0].getRank())
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
})
|
})
|
||||||
@ -306,7 +306,7 @@ export async function issueStatusSort (
|
|||||||
listIssueStatusOrder.indexOf(a.values[0].category as Ref<StatusCategory>) -
|
listIssueStatusOrder.indexOf(a.values[0].category as Ref<StatusCategory>) -
|
||||||
listIssueStatusOrder.indexOf(b.values[0].category as Ref<StatusCategory>)
|
listIssueStatusOrder.indexOf(b.values[0].category as Ref<StatusCategory>)
|
||||||
if (res === 0) {
|
if (res === 0) {
|
||||||
return a.values[0].rank.localeCompare(b.values[0].rank)
|
return a.values[0].getRank().localeCompare(b.values[0].getRank())
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
})
|
})
|
||||||
|
@ -14,11 +14,13 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||||
import type {
|
import {
|
||||||
AttachedDoc,
|
AttachedDoc,
|
||||||
Attribute,
|
Attribute,
|
||||||
Class,
|
Class,
|
||||||
Doc,
|
Doc,
|
||||||
|
DocManager,
|
||||||
|
IdMap,
|
||||||
Markup,
|
Markup,
|
||||||
Ref,
|
Ref,
|
||||||
RelatedDocument,
|
RelatedDocument,
|
||||||
@ -26,7 +28,8 @@ import type {
|
|||||||
Status,
|
Status,
|
||||||
StatusCategory,
|
StatusCategory,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
Type
|
Type,
|
||||||
|
WithLookup
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
|
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
@ -310,6 +313,29 @@ export interface Component extends Doc {
|
|||||||
attachments?: number
|
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
|
* @public
|
||||||
*/
|
*/
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
"@hcengineering/presentation": "^0.6.2",
|
"@hcengineering/presentation": "^0.6.2",
|
||||||
"@hcengineering/setting": "^0.6.7",
|
"@hcengineering/setting": "^0.6.7",
|
||||||
"@hcengineering/text-editor": "^0.6.0",
|
"@hcengineering/text-editor": "^0.6.0",
|
||||||
|
"@hcengineering/query": "^0.6.5",
|
||||||
"fast-equals": "^2.0.3"
|
"fast-equals": "^2.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import core, {
|
|||||||
Ref
|
Ref
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import type { Action, ViewAction, ViewActionInput, ViewContextType } from '@hcengineering/view'
|
import { Action, ViewAction, ViewActionInput, ViewContextType } from '@hcengineering/view'
|
||||||
import view from './plugin'
|
import view from './plugin'
|
||||||
import { FocusSelection } from './selection'
|
import { FocusSelection } from './selection'
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
import view from '../plugin'
|
import view from '../plugin'
|
||||||
import { getObjectLinkFragment } from '../utils'
|
import { getObjectLinkFragment } from '../utils'
|
||||||
|
|
||||||
export let object: Doc
|
export let object: Doc | undefined
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let onClick: ((event: MouseEvent) => void) | undefined = undefined
|
export let onClick: ((event: MouseEvent) => void) | undefined = undefined
|
||||||
export let noUnderline = false
|
export let noUnderline = false
|
||||||
|
@ -13,9 +13,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { Doc, FindResult, getObjectValue, Ref, RefTo, SortingOrder, Space, Status } from '@hcengineering/core'
|
import core, { Doc, FindResult, getObjectValue, Ref, RefTo, SortingOrder, Space } from '@hcengineering/core'
|
||||||
import { translate } from '@hcengineering/platform'
|
import { getResource, translate } from '@hcengineering/platform'
|
||||||
import presentation, { getClient, statusStore } from '@hcengineering/presentation'
|
import presentation, { getClient } from '@hcengineering/presentation'
|
||||||
import ui, {
|
import ui, {
|
||||||
addNotification,
|
addNotification,
|
||||||
deviceOptionsStore,
|
deviceOptionsStore,
|
||||||
@ -27,7 +27,7 @@
|
|||||||
Loading,
|
Loading,
|
||||||
resizeObserver
|
resizeObserver
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { Filter } from '@hcengineering/view'
|
import { Filter, GrouppingManager } from '@hcengineering/view'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { FILTER_DEBOUNCE_MS, sortFilterValues } from '../../filter'
|
import { FILTER_DEBOUNCE_MS, sortFilterValues } from '../../filter'
|
||||||
import view from '../../plugin'
|
import view from '../../plugin'
|
||||||
@ -49,32 +49,18 @@
|
|||||||
|
|
||||||
let values: (Doc | undefined | null)[] = []
|
let values: (Doc | undefined | null)[] = []
|
||||||
let objectsPromise: Promise<FindResult<Doc>> | undefined
|
let objectsPromise: Promise<FindResult<Doc>> | undefined
|
||||||
|
let grouppingManager: GrouppingManager | undefined
|
||||||
|
|
||||||
const targets = new Set<any>()
|
const targets = new Set<any>()
|
||||||
$: targetClass = (filter.key.attribute.type as RefTo<Doc>).to
|
$: targetClass = (filter.key.attribute.type as RefTo<Doc>).to
|
||||||
$: clazz = hierarchy.getClass(targetClass)
|
$: clazz = hierarchy.getClass(targetClass)
|
||||||
|
$: mixin = hierarchy.classHierarchyMixin(targetClass, view.mixin.Groupping)
|
||||||
$: isStatus = client.getHierarchy().isDerived(targetClass, core.class.Status) ?? false
|
$: if (mixin?.grouppingManager !== undefined) {
|
||||||
|
getResource(mixin.grouppingManager).then((mgr) => (grouppingManager = mgr))
|
||||||
|
}
|
||||||
|
|
||||||
let filterUpdateTimeout: number | undefined
|
let filterUpdateTimeout: number | undefined
|
||||||
|
|
||||||
const groupValues = (val: Status[]): Doc[] => {
|
|
||||||
const statuses = val
|
|
||||||
const result: Doc[] = []
|
|
||||||
const unique = [...new Set(val.map((v) => v.name.trim().toLocaleLowerCase()))]
|
|
||||||
unique.forEach((label, i) => {
|
|
||||||
let exists = false
|
|
||||||
statuses.forEach((state) => {
|
|
||||||
if (state.name.trim().toLocaleLowerCase() === label) {
|
|
||||||
if (!exists) {
|
|
||||||
result[i] = state
|
|
||||||
exists = targets.has(state?._id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getValues (search: string): Promise<void> {
|
async function getValues (search: string): Promise<void> {
|
||||||
if (objectsPromise) {
|
if (objectsPromise) {
|
||||||
await objectsPromise
|
await objectsPromise
|
||||||
@ -108,8 +94,8 @@
|
|||||||
const options = clazz.sortingKey !== undefined ? { sort: { [clazz.sortingKey]: SortingOrder.Ascending } } : {}
|
const options = clazz.sortingKey !== undefined ? { sort: { [clazz.sortingKey]: SortingOrder.Ascending } } : {}
|
||||||
objectsPromise = client.findAll(targetClass, resultQuery, options)
|
objectsPromise = client.findAll(targetClass, resultQuery, options)
|
||||||
values = await objectsPromise
|
values = await objectsPromise
|
||||||
if (isStatus) {
|
if (grouppingManager !== undefined) {
|
||||||
values = groupValues(values as Status[])
|
values = grouppingManager.groupValues(values as Doc[], targets)
|
||||||
}
|
}
|
||||||
if (targets.has(undefined)) {
|
if (targets.has(undefined)) {
|
||||||
values.unshift(undefined)
|
values.unshift(undefined)
|
||||||
@ -130,13 +116,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isSelected (value: Doc | undefined | null, values: any[]): boolean {
|
function isSelected (value: Doc | undefined | null, values: any[]): boolean {
|
||||||
if (isStatus) {
|
if (grouppingManager !== undefined) {
|
||||||
const statusSet = new Set(
|
return grouppingManager.hasValue(value, values)
|
||||||
$statusStore
|
|
||||||
.filter((it) => it.name.trim().toLocaleLowerCase() === (value as Status)?.name?.trim()?.toLocaleLowerCase())
|
|
||||||
.map((it) => it._id)
|
|
||||||
)
|
|
||||||
return values.some((it) => statusSet.has(it))
|
|
||||||
}
|
}
|
||||||
return values.includes(value?._id ?? value)
|
return values.includes(value?._id ?? value)
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { CategoryType, Class, Doc, DocumentQuery, generateId, Lookup, Ref, Space } from '@hcengineering/core'
|
import { CategoryType, Class, Doc, DocumentQuery, generateId, Lookup, Ref, Space } from '@hcengineering/core'
|
||||||
import { getResource, IntlString } from '@hcengineering/platform'
|
import { getResource, IntlString } from '@hcengineering/platform'
|
||||||
import { getClient, statusStore } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { AnyComponent, AnySvelteComponent } from '@hcengineering/ui'
|
import { AnyComponent, AnySvelteComponent } from '@hcengineering/ui'
|
||||||
import { AttributeModel, BuildModelKey, CategoryOption, ViewOptionModel, ViewOptions } from '@hcengineering/view'
|
import { AttributeModel, BuildModelKey, CategoryOption, ViewOptionModel, ViewOptions } from '@hcengineering/view'
|
||||||
import { createEventDispatcher, onDestroy, SvelteComponentTyped } from 'svelte'
|
import { createEventDispatcher, onDestroy, SvelteComponentTyped } from 'svelte'
|
||||||
@ -84,14 +84,14 @@
|
|||||||
viewOptions: ViewOptions,
|
viewOptions: ViewOptions,
|
||||||
viewOptionsModel: ViewOptionModel[] | undefined
|
viewOptionsModel: ViewOptionModel[] | undefined
|
||||||
) {
|
) {
|
||||||
categories = await getCategories(client, _class, docs, groupByKey, $statusStore)
|
categories = await getCategories(client, _class, docs, groupByKey)
|
||||||
if (level === 0) {
|
if (level === 0) {
|
||||||
for (const viewOption of viewOptionsModel ?? []) {
|
for (const viewOption of viewOptionsModel ?? []) {
|
||||||
if (viewOption.actionTarget !== 'category') continue
|
if (viewOption.actionTarget !== 'category') continue
|
||||||
const categoryFunc = viewOption as CategoryOption
|
const categoryFunc = viewOption as CategoryOption
|
||||||
if (viewOptions[viewOption.key] ?? viewOption.defaultValue) {
|
if (viewOptions[viewOption.key] ?? viewOption.defaultValue) {
|
||||||
const f = await getResource(categoryFunc.action)
|
const f = await getResource(categoryFunc.action)
|
||||||
const res = hierarchy.clone(await f(_class, query, groupByKey, update, queryId, $statusStore))
|
const res = hierarchy.clone(await f(_class, query, groupByKey, update, queryId))
|
||||||
if (res !== undefined) {
|
if (res !== undefined) {
|
||||||
categories = concatCategories(res, categories)
|
categories = concatCategories(res, categories)
|
||||||
return
|
return
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Class, Doc, DocumentUpdate, Lookup, PrimitiveType, Ref, Space, StatusValue } from '@hcengineering/core'
|
import { AggregateValue, Class, Doc, DocumentUpdate, Lookup, PrimitiveType, Ref, Space } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { DocWithRank, calcRank } from '@hcengineering/task'
|
import { DocWithRank, calcRank } from '@hcengineering/task'
|
||||||
@ -34,7 +34,7 @@
|
|||||||
import ListHeader from './ListHeader.svelte'
|
import ListHeader from './ListHeader.svelte'
|
||||||
import ListItem from './ListItem.svelte'
|
import ListItem from './ListItem.svelte'
|
||||||
|
|
||||||
export let category: PrimitiveType | StatusValue
|
export let category: PrimitiveType | AggregateValue
|
||||||
export let headerComponent: AttributeModel | undefined
|
export let headerComponent: AttributeModel | undefined
|
||||||
export let singleCat: boolean
|
export let singleCat: boolean
|
||||||
export let oneCat: boolean
|
export let oneCat: boolean
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Doc, Ref, Space } from '@hcengineering/core'
|
import { AggregateValue, Doc, PrimitiveType, Ref, Space } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import ui, {
|
import ui, {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
@ -40,7 +40,7 @@
|
|||||||
import { noCategory } from '../../viewOptions'
|
import { noCategory } from '../../viewOptions'
|
||||||
|
|
||||||
export let groupByKey: string
|
export let groupByKey: string
|
||||||
export let category: any
|
export let category: PrimitiveType | AggregateValue
|
||||||
export let headerComponent: AttributeModel | undefined
|
export let headerComponent: AttributeModel | undefined
|
||||||
export let space: Ref<Space> | undefined
|
export let space: Ref<Space> | undefined
|
||||||
export let limited: number
|
export let limited: number
|
||||||
|
@ -16,15 +16,17 @@
|
|||||||
import { Ref, Status, StatusValue } from '@hcengineering/core'
|
import { Ref, Status, StatusValue } from '@hcengineering/core'
|
||||||
import { Asset } from '@hcengineering/platform'
|
import { Asset } from '@hcengineering/platform'
|
||||||
import { AnySvelteComponent } from '@hcengineering/ui'
|
import { AnySvelteComponent } from '@hcengineering/ui'
|
||||||
import { statusStore } from '@hcengineering/presentation'
|
|
||||||
|
|
||||||
import StatusPresenter from './StatusPresenter.svelte'
|
import StatusPresenter from './StatusPresenter.svelte'
|
||||||
|
import { statusStore } from '../../status'
|
||||||
|
|
||||||
export let value: Ref<Status> | StatusValue | undefined
|
export let value: Ref<Status> | StatusValue | undefined
|
||||||
export let size: 'small' | 'medium' = 'medium'
|
export let size: 'small' | 'medium' = 'medium'
|
||||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||||
|
|
||||||
|
$: statusValue = $statusStore.get(typeof value === 'string' ? value : (value?.values?.[0]?._id as Ref<Status>))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
<StatusPresenter value={$statusStore.get(typeof value === 'string' ? value : value.values?.[0]._id)} {size} {icon} />
|
<StatusPresenter value={statusValue} {size} {icon} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -104,6 +104,8 @@ import {
|
|||||||
import { IndexedDocumentPreview } from '@hcengineering/presentation'
|
import { IndexedDocumentPreview } from '@hcengineering/presentation'
|
||||||
import { statusSort } from './utils'
|
import { statusSort } from './utils'
|
||||||
import { showEmptyGroups } from './viewOptions'
|
import { showEmptyGroups } from './viewOptions'
|
||||||
|
import { AggregationMiddleware } from './middleware'
|
||||||
|
import { grouppingStatusManager, StatusAggregationManager } from './status'
|
||||||
export { getActions, invokeAction } from './actions'
|
export { getActions, invokeAction } from './actions'
|
||||||
export { default as ActionHandler } from './components/ActionHandler.svelte'
|
export { default as ActionHandler } from './components/ActionHandler.svelte'
|
||||||
export { default as AddSavedView } from './components/filter/AddSavedView.svelte'
|
export { default as AddSavedView } from './components/filter/AddSavedView.svelte'
|
||||||
@ -129,6 +131,8 @@ export { default as ParentsNavigator } from './components/ParentsNavigator.svelt
|
|||||||
export * from './filter'
|
export * from './filter'
|
||||||
export * from './selection'
|
export * from './selection'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
|
export * from './status'
|
||||||
|
export * from './middleware'
|
||||||
export {
|
export {
|
||||||
buildModel,
|
buildModel,
|
||||||
getActiveViewletId,
|
getActiveViewletId,
|
||||||
@ -257,6 +261,11 @@ export default async (): Promise<Resources> => ({
|
|||||||
FilterDateMonth: dateMonth,
|
FilterDateMonth: dateMonth,
|
||||||
FilterDateNextMonth: dateNextMonth,
|
FilterDateNextMonth: dateNextMonth,
|
||||||
FilterDateNotSpecified: dateNotSpecified,
|
FilterDateNotSpecified: dateNotSpecified,
|
||||||
FilterDateCustom: dateCustom
|
FilterDateCustom: dateCustom,
|
||||||
|
CreateDocMiddleware: AggregationMiddleware.create
|
||||||
|
},
|
||||||
|
aggregation: {
|
||||||
|
CreateStatusAggregationManager: StatusAggregationManager.create,
|
||||||
|
GrouppingStatusManager: grouppingStatusManager
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
234
plugins/view-resources/src/middleware.ts
Normal file
234
plugins/view-resources/src/middleware.ts
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
import core, {
|
||||||
|
Doc,
|
||||||
|
Ref,
|
||||||
|
AnyAttribute,
|
||||||
|
Class,
|
||||||
|
DocumentQuery,
|
||||||
|
FindOptions,
|
||||||
|
Client,
|
||||||
|
Tx,
|
||||||
|
TxResult,
|
||||||
|
FindResult,
|
||||||
|
Attribute,
|
||||||
|
Hierarchy,
|
||||||
|
RefTo,
|
||||||
|
generateId
|
||||||
|
} from '@hcengineering/core'
|
||||||
|
import { BasePresentationMiddleware, PresentationMiddleware } from '@hcengineering/presentation'
|
||||||
|
import view, { AggregationManager } from '@hcengineering/view'
|
||||||
|
import { getResource } from '@hcengineering/platform'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface DocSubScriber<T extends Doc = Doc> {
|
||||||
|
attributes: Array<Ref<AnyAttribute>>
|
||||||
|
|
||||||
|
_class: Ref<Class<T>>
|
||||||
|
query: DocumentQuery<T>
|
||||||
|
options?: FindOptions<T>
|
||||||
|
|
||||||
|
refresh: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export class AggregationMiddleware extends BasePresentationMiddleware implements PresentationMiddleware {
|
||||||
|
mgrs: Map<Ref<Class<Doc>>, AggregationManager> = new Map<Ref<Class<Doc>>, AggregationManager>()
|
||||||
|
docs: Doc[] | undefined
|
||||||
|
|
||||||
|
subscribers: Map<string, DocSubScriber> = new Map()
|
||||||
|
private constructor (client: Client, next?: PresentationMiddleware) {
|
||||||
|
super(client, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create (client: Client, next?: PresentationMiddleware): AggregationMiddleware {
|
||||||
|
return new AggregationMiddleware(client, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
async notifyTx (tx: Tx): Promise<void> {
|
||||||
|
const promises: Array<Promise<void>> = []
|
||||||
|
for (const [, value] of this.mgrs) {
|
||||||
|
promises.push(value.notifyTx(tx))
|
||||||
|
}
|
||||||
|
await Promise.all(promises)
|
||||||
|
await this.provideNotifyTx(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
async close (): Promise<void> {
|
||||||
|
this.mgrs.forEach((mgr) => mgr.close())
|
||||||
|
return await this.provideClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
async tx (tx: Tx): Promise<TxResult> {
|
||||||
|
return await this.provideTx(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshSubscribers (): void {
|
||||||
|
for (const s of this.subscribers.values()) {
|
||||||
|
// TODO: Do something more smart and track if used component field is changed.
|
||||||
|
s.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async subscribe<T extends Doc>(
|
||||||
|
_class: Ref<Class<T>>,
|
||||||
|
query: DocumentQuery<T>,
|
||||||
|
options: FindOptions<T> | undefined,
|
||||||
|
refresh: () => void
|
||||||
|
): Promise<{
|
||||||
|
unsubscribe: () => void
|
||||||
|
query?: DocumentQuery<T>
|
||||||
|
options?: FindOptions<T>
|
||||||
|
}> {
|
||||||
|
const ret = await this.provideSubscribe(_class, query, options, refresh)
|
||||||
|
const h = this.client.getHierarchy()
|
||||||
|
|
||||||
|
const id = generateId()
|
||||||
|
const s: DocSubScriber<T> = {
|
||||||
|
_class,
|
||||||
|
query,
|
||||||
|
refresh,
|
||||||
|
options,
|
||||||
|
attributes: []
|
||||||
|
}
|
||||||
|
const statusFields: Array<Attribute<Doc>> = []
|
||||||
|
const allAttrs = h.getAllAttributes(_class)
|
||||||
|
|
||||||
|
const updatedQuery: DocumentQuery<T> = { ...(ret.query ?? query) }
|
||||||
|
const finalOptions = { ...(ret.options ?? options ?? {}) }
|
||||||
|
|
||||||
|
await this.updateQueryOptions<T>(allAttrs, h, statusFields, updatedQuery, finalOptions)
|
||||||
|
|
||||||
|
if (statusFields.length > 0) {
|
||||||
|
this.subscribers.set(id, s)
|
||||||
|
return {
|
||||||
|
unsubscribe: () => {
|
||||||
|
ret.unsubscribe()
|
||||||
|
this.subscribers.delete(id)
|
||||||
|
},
|
||||||
|
query: updatedQuery,
|
||||||
|
options: finalOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { unsubscribe: (await ret).unsubscribe }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getAggregationManager (_class: Ref<Class<Doc>>): Promise<AggregationManager | 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) {
|
||||||
|
const f = await getResource(mixin.createAggregationManager)
|
||||||
|
mgr = f(this.client, this.refreshSubscribers)
|
||||||
|
this.mgrs.set(_class, mgr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mgr
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll<T extends Doc>(
|
||||||
|
_class: Ref<Class<T>>,
|
||||||
|
query: DocumentQuery<T>,
|
||||||
|
options?: FindOptions<T> | undefined
|
||||||
|
): Promise<FindResult<T>> {
|
||||||
|
const docFields: Array<Attribute<Doc>> = []
|
||||||
|
const h = this.client.getHierarchy()
|
||||||
|
const allAttrs = h.getAllAttributes(_class)
|
||||||
|
const finalOptions = options ?? {}
|
||||||
|
|
||||||
|
await this.updateQueryOptions<T>(allAttrs, h, docFields, query, finalOptions)
|
||||||
|
|
||||||
|
const result = await this.provideFindAll(_class, query, finalOptions)
|
||||||
|
// We need to add $
|
||||||
|
if (docFields.length > 0) {
|
||||||
|
// We need to update $lookup for doc fields and provide $doc group fields.
|
||||||
|
for (const attr of docFields) {
|
||||||
|
for (const r of result) {
|
||||||
|
const resultDoc = Hierarchy.toDoc(r)
|
||||||
|
if (resultDoc.$lookup === undefined) {
|
||||||
|
resultDoc.$lookup = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mgr = await this.getAggregationManager((attr.type as RefTo<Doc>).to)
|
||||||
|
if (mgr !== undefined) {
|
||||||
|
await mgr.updateLookup(resultDoc, attr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateQueryOptions<T extends Doc>(
|
||||||
|
allAttrs: Map<string, AnyAttribute>,
|
||||||
|
h: Hierarchy,
|
||||||
|
docFields: Array<Attribute<Doc>>,
|
||||||
|
query: DocumentQuery<T>,
|
||||||
|
finalOptions: FindOptions<T>
|
||||||
|
): Promise<void> {
|
||||||
|
for (const attr of allAttrs.values()) {
|
||||||
|
try {
|
||||||
|
if (attr.type._class !== core.class.RefTo) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const mgr = await this.getAggregationManager((attr.type as RefTo<Doc>).to)
|
||||||
|
if (mgr === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (h.isDerived((attr.type as RefTo<Doc>).to, mgr.getAttrClass())) {
|
||||||
|
let target: Array<Ref<Doc>> = []
|
||||||
|
let targetNin: Array<Ref<Doc>> = []
|
||||||
|
docFields.push(attr)
|
||||||
|
const v = (query as any)[attr.name]
|
||||||
|
|
||||||
|
if (v != null) {
|
||||||
|
// Only add filter if we have filer inside.
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
target.push(v as Ref<Doc>)
|
||||||
|
} else {
|
||||||
|
if (v.$in !== undefined) {
|
||||||
|
target.push(...v.$in)
|
||||||
|
} else if (v.$nin !== undefined) {
|
||||||
|
targetNin.push(...v.$nin)
|
||||||
|
} else if (v.$ne !== undefined) {
|
||||||
|
targetNin.push(v.$ne)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all similar name statues for same attribute name.
|
||||||
|
target = await mgr.categorize(target, attr)
|
||||||
|
targetNin = await mgr.categorize(targetNin, attr)
|
||||||
|
if (target.length > 0 || targetNin.length > 0) {
|
||||||
|
;(query as any)[attr.name] = {}
|
||||||
|
if (target.length > 0) {
|
||||||
|
;(query as any)[attr.name].$in = target
|
||||||
|
}
|
||||||
|
if (targetNin.length > 0) {
|
||||||
|
;(query as any)[attr.name].$nin = targetNin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (finalOptions.lookup !== undefined) {
|
||||||
|
// Remove lookups by status field
|
||||||
|
if ((finalOptions.lookup as any)[attr.name] !== undefined) {
|
||||||
|
const { [attr.name]: _, ...newLookup } = finalOptions.lookup as any
|
||||||
|
finalOptions.lookup = newLookup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update sorting if defined.
|
||||||
|
if (mgr.updateSorting !== undefined) {
|
||||||
|
await mgr.updateSorting(finalOptions, attr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,9 +14,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { IntlString, mergeIds } from '@hcengineering/platform'
|
import { IntlString, Resource, mergeIds } from '@hcengineering/platform'
|
||||||
|
import { PresentationMiddlewareCreator } from '@hcengineering/presentation'
|
||||||
import { AnyComponent } from '@hcengineering/ui'
|
import { AnyComponent } from '@hcengineering/ui'
|
||||||
import view, { SortFunc, viewId } from '@hcengineering/view'
|
import view, { CreateAggregationManagerFunc, GrouppingManagerResource, SortFunc, viewId } from '@hcengineering/view'
|
||||||
|
|
||||||
export default mergeIds(viewId, view, {
|
export default mergeIds(viewId, view, {
|
||||||
component: {
|
component: {
|
||||||
@ -94,6 +95,11 @@ export default mergeIds(viewId, view, {
|
|||||||
Show: '' as IntlString
|
Show: '' as IntlString
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
StatusSort: '' as SortFunc
|
StatusSort: '' as SortFunc,
|
||||||
|
CreateDocMiddleware: '' as Resource<PresentationMiddlewareCreator>
|
||||||
|
},
|
||||||
|
aggregation: {
|
||||||
|
CreateStatusAggregationManager: '' as CreateAggregationManagerFunc,
|
||||||
|
GrouppingStatusManager: '' as GrouppingManagerResource
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
283
plugins/view-resources/src/status.ts
Normal file
283
plugins/view-resources/src/status.ts
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2023 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import core, {
|
||||||
|
AggregateValue,
|
||||||
|
AggregateValueData,
|
||||||
|
AnyAttribute,
|
||||||
|
Attribute,
|
||||||
|
Class,
|
||||||
|
Client,
|
||||||
|
Doc,
|
||||||
|
DocumentQuery,
|
||||||
|
FindOptions,
|
||||||
|
Hierarchy,
|
||||||
|
Ref,
|
||||||
|
SortingOrder,
|
||||||
|
SortingRules,
|
||||||
|
Space,
|
||||||
|
Status,
|
||||||
|
StatusManager,
|
||||||
|
StatusValue,
|
||||||
|
Tx,
|
||||||
|
WithLookup,
|
||||||
|
matchQuery
|
||||||
|
} from '@hcengineering/core'
|
||||||
|
import { LiveQuery } from '@hcengineering/query'
|
||||||
|
import { AggregationManager, GrouppingManager } from '@hcengineering/view'
|
||||||
|
import { get, writable } from 'svelte/store'
|
||||||
|
|
||||||
|
// Issue status live query
|
||||||
|
export const statusStore = writable<StatusManager>(new StatusManager([]))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export class StatusAggregationManager implements AggregationManager {
|
||||||
|
docs: Doc[] | undefined
|
||||||
|
mgr: StatusManager | Promise<StatusManager> | 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): StatusAggregationManager {
|
||||||
|
return new StatusAggregationManager(client, lqCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getManager (): Promise<StatusManager> {
|
||||||
|
if (this.mgr !== undefined) {
|
||||||
|
if (this.mgr instanceof Promise) {
|
||||||
|
this.mgr = await this.mgr
|
||||||
|
}
|
||||||
|
return this.mgr
|
||||||
|
}
|
||||||
|
this.mgr = new Promise<StatusManager>((resolve) => {
|
||||||
|
this.query = this.lq.query(
|
||||||
|
core.class.Status,
|
||||||
|
{},
|
||||||
|
(res) => {
|
||||||
|
const first = this.docs === undefined
|
||||||
|
this.docs = res
|
||||||
|
this.mgr = new StatusManager(res)
|
||||||
|
statusStore.set(this.mgr)
|
||||||
|
if (!first) {
|
||||||
|
this.lqCallback()
|
||||||
|
}
|
||||||
|
resolve(this.mgr)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lookup: {
|
||||||
|
category: core.class.StatusCategory
|
||||||
|
},
|
||||||
|
sort: {
|
||||||
|
rank: 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 core.class.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
async categorize (target: Array<Ref<Doc>>, attr: AnyAttribute): Promise<Array<Ref<Doc>>> {
|
||||||
|
const mgr = await this.getManager()
|
||||||
|
for (const sid of [...target]) {
|
||||||
|
const s = mgr.getIdMap().get(sid as Ref<Status>) as WithLookup<Status>
|
||||||
|
if (s !== undefined) {
|
||||||
|
let statuses = mgr.getDocs()
|
||||||
|
statuses = statuses.filter(
|
||||||
|
(it) =>
|
||||||
|
it.ofAttribute === attr._id &&
|
||||||
|
it.name.toLowerCase().trim() === s.name.toLowerCase().trim() &&
|
||||||
|
it._id !== s._id
|
||||||
|
)
|
||||||
|
target.push(...statuses.map((it) => it._id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target.filter((it, idx, arr) => arr.indexOf(it) === idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateLookup (resultDoc: WithLookup<Doc>, attr: Attribute<Doc>): Promise<void> {
|
||||||
|
const value = (resultDoc as any)[attr.name]
|
||||||
|
const doc = (await this.getManager()).getIdMap().get(value)
|
||||||
|
if (doc !== undefined) {
|
||||||
|
;(resultDoc.$lookup as any)[attr.name] = doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSorting<T extends Doc>(finalOptions: FindOptions<T>, attr: AnyAttribute): Promise<void> {
|
||||||
|
const attrSort = finalOptions.sort?.[attr.name]
|
||||||
|
if (attrSort !== undefined && typeof attrSort !== 'object') {
|
||||||
|
// Fill custom sorting.
|
||||||
|
let statuses = (await this.getManager()).getDocs()
|
||||||
|
statuses = statuses.filter((it) => it.ofAttribute === attr._id)
|
||||||
|
statuses.sort((a, b) => {
|
||||||
|
let ret = 0
|
||||||
|
if (a.category !== undefined && b.category !== undefined) {
|
||||||
|
ret = (a.$lookup?.category?.order ?? 0) - (b.$lookup?.category?.order ?? 0)
|
||||||
|
}
|
||||||
|
if (ret === 0) {
|
||||||
|
if (a.name.toLowerCase().trim() === b.name.toLowerCase().trim()) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
ret = a.rank.localeCompare(b.rank)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
if (finalOptions.sort === undefined) {
|
||||||
|
finalOptions.sort = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rules: SortingRules<any> = {
|
||||||
|
order: attrSort,
|
||||||
|
cases: statuses.map((it, idx) => ({ query: it._id, index: idx })),
|
||||||
|
default: statuses.length + 1
|
||||||
|
}
|
||||||
|
;(finalOptions.sort as any)[attr.name] = rules
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export const grouppingStatusManager: GrouppingManager = {
|
||||||
|
groupByCategories: groupByStatusCategories,
|
||||||
|
groupValues: groupStatusValues,
|
||||||
|
groupValuesWithEmpty: groupStatusValuesWithEmpty,
|
||||||
|
hasValue: hasStatusValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function groupByStatusCategories (categories: any[]): AggregateValue[] {
|
||||||
|
const mgr = get(statusStore)
|
||||||
|
|
||||||
|
const existingCategories: AggregateValue[] = []
|
||||||
|
const statusMap = new Map<string, AggregateValue>()
|
||||||
|
|
||||||
|
const usedSpaces = new Set<Ref<Space>>()
|
||||||
|
const statusesList: Array<WithLookup<Status>> = []
|
||||||
|
for (const v of categories) {
|
||||||
|
const status = mgr.getIdMap().get(v)
|
||||||
|
if (status !== undefined) {
|
||||||
|
statusesList.push(status)
|
||||||
|
usedSpaces.add(status.space)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const status of statusesList) {
|
||||||
|
if (status !== undefined) {
|
||||||
|
let fst = statusMap.get(status.name.toLowerCase().trim())
|
||||||
|
if (fst === undefined) {
|
||||||
|
const statuses = mgr
|
||||||
|
.getDocs()
|
||||||
|
.filter(
|
||||||
|
(it) =>
|
||||||
|
it.ofAttribute === status.ofAttribute &&
|
||||||
|
it.name.toLowerCase().trim() === status.name.toLowerCase().trim() &&
|
||||||
|
(categories.includes(it._id) || usedSpaces.has(it.space))
|
||||||
|
)
|
||||||
|
.sort((a, b) => a.rank.localeCompare(b.rank))
|
||||||
|
.map((it) => new AggregateValueData(it.name, it._id, it.space, it.rank, it.category))
|
||||||
|
fst = new StatusValue(status.name, status.color, statuses)
|
||||||
|
statusMap.set(status.name.toLowerCase().trim(), fst)
|
||||||
|
existingCategories.push(fst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return existingCategories
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function groupStatusValues (val: Doc[], targets: Set<any>): Doc[] {
|
||||||
|
const values = val
|
||||||
|
const result: Doc[] = []
|
||||||
|
const unique = [...new Set(val.map((v) => (v as Status).name.trim().toLocaleLowerCase()))]
|
||||||
|
unique.forEach((label, i) => {
|
||||||
|
let exists = false
|
||||||
|
values.forEach((value) => {
|
||||||
|
if ((value as Status).name.trim().toLocaleLowerCase() === label) {
|
||||||
|
if (!exists) {
|
||||||
|
result[i] = value
|
||||||
|
exists = targets.has(value?._id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function hasStatusValue (value: Doc | undefined | null, values: any[]): boolean {
|
||||||
|
const mgr = get(statusStore)
|
||||||
|
const statusSet = new Set(
|
||||||
|
mgr
|
||||||
|
.filter((it) => it.name.trim().toLocaleLowerCase() === (value as Status)?.name?.trim()?.toLocaleLowerCase())
|
||||||
|
.map((it) => it._id)
|
||||||
|
)
|
||||||
|
return values.some((it) => statusSet.has(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function groupStatusValuesWithEmpty (
|
||||||
|
hierarchy: Hierarchy,
|
||||||
|
_class: Ref<Class<Doc>>,
|
||||||
|
key: string,
|
||||||
|
query: DocumentQuery<Doc> | undefined
|
||||||
|
): Array<Ref<Doc>> {
|
||||||
|
const mgr = get(statusStore)
|
||||||
|
const attr = hierarchy.getAttribute(_class, key)
|
||||||
|
// We do not need extensions for all status categories.
|
||||||
|
let statusList = mgr.filter((it) => {
|
||||||
|
return it.ofAttribute === attr._id
|
||||||
|
})
|
||||||
|
if (query !== undefined) {
|
||||||
|
const { [key]: st, space } = query
|
||||||
|
const resQuery: DocumentQuery<Doc> = {}
|
||||||
|
if (space !== undefined) {
|
||||||
|
resQuery.space = space
|
||||||
|
}
|
||||||
|
if (st !== undefined) {
|
||||||
|
resQuery._id = st
|
||||||
|
}
|
||||||
|
statusList = matchQuery<Doc>(statusList, resQuery, _class, hierarchy) as unknown as Array<WithLookup<Status>>
|
||||||
|
}
|
||||||
|
return statusList.map((it) => it._id)
|
||||||
|
}
|
@ -17,6 +17,7 @@
|
|||||||
import core, {
|
import core, {
|
||||||
AccountRole,
|
AccountRole,
|
||||||
AttachedDoc,
|
AttachedDoc,
|
||||||
|
AggregateValue,
|
||||||
CategoryType,
|
CategoryType,
|
||||||
Class,
|
Class,
|
||||||
Client,
|
Client,
|
||||||
@ -34,10 +35,7 @@ import core, {
|
|||||||
ReverseLookups,
|
ReverseLookups,
|
||||||
Space,
|
Space,
|
||||||
Status,
|
Status,
|
||||||
StatusManager,
|
TxOperations
|
||||||
StatusValue,
|
|
||||||
TxOperations,
|
|
||||||
WithLookup
|
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import type { IntlString } from '@hcengineering/platform'
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
@ -59,6 +57,7 @@ import {
|
|||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import type { BuildModelOptions, Viewlet, ViewletDescriptor } from '@hcengineering/view'
|
import type { BuildModelOptions, Viewlet, ViewletDescriptor } from '@hcengineering/view'
|
||||||
import view, { AttributeModel, BuildModelKey } from '@hcengineering/view'
|
import view, { AttributeModel, BuildModelKey } from '@hcengineering/view'
|
||||||
|
|
||||||
import { get, writable } from 'svelte/store'
|
import { get, writable } from 'svelte/store'
|
||||||
import plugin from './plugin'
|
import plugin from './plugin'
|
||||||
import { noCategory } from './viewOptions'
|
import { noCategory } from './viewOptions'
|
||||||
@ -597,7 +596,7 @@ export function groupBy<T extends Doc> (docs: T[], key: string, categories?: Cat
|
|||||||
*/
|
*/
|
||||||
export function getGroupByValues<T extends Doc> (groupByDocs: Record<any, T[]>, category: CategoryType): T[] {
|
export function getGroupByValues<T extends Doc> (groupByDocs: Record<any, T[]>, category: CategoryType): T[] {
|
||||||
if (typeof category === 'object') {
|
if (typeof category === 'object') {
|
||||||
return groupByDocs[category.name] ?? []
|
return groupByDocs[category.name as any] ?? []
|
||||||
} else {
|
} else {
|
||||||
return groupByDocs[category as any] ?? []
|
return groupByDocs[category as any] ?? []
|
||||||
}
|
}
|
||||||
@ -612,7 +611,7 @@ export function setGroupByValues (
|
|||||||
docs: Doc[]
|
docs: Doc[]
|
||||||
): void {
|
): void {
|
||||||
if (typeof category === 'object') {
|
if (typeof category === 'object') {
|
||||||
groupByDocs[category.name] = docs
|
groupByDocs[category.name as any] = docs
|
||||||
} else if (category !== undefined) {
|
} else if (category !== undefined) {
|
||||||
groupByDocs[category] = docs
|
groupByDocs[category] = docs
|
||||||
}
|
}
|
||||||
@ -626,8 +625,7 @@ export async function groupByCategory (
|
|||||||
client: TxOperations,
|
client: TxOperations,
|
||||||
_class: Ref<Class<Doc>>,
|
_class: Ref<Class<Doc>>,
|
||||||
key: string,
|
key: string,
|
||||||
categories: any[],
|
categories: CategoryType[],
|
||||||
mgr: StatusManager,
|
|
||||||
viewletDescriptorId?: Ref<ViewletDescriptor>
|
viewletDescriptorId?: Ref<ViewletDescriptor>
|
||||||
): Promise<CategoryType[]> {
|
): Promise<CategoryType[]> {
|
||||||
const h = client.getHierarchy()
|
const h = client.getHierarchy()
|
||||||
@ -636,13 +634,12 @@ export async function groupByCategory (
|
|||||||
if (key === noCategory) return [undefined]
|
if (key === noCategory) return [undefined]
|
||||||
|
|
||||||
const attrClass = getAttributePresenterClass(h, attr).attrClass
|
const attrClass = getAttributePresenterClass(h, attr).attrClass
|
||||||
|
const mixin = h.classHierarchyMixin(attrClass, view.mixin.Groupping)
|
||||||
const isStatusField = h.isDerived(attrClass, core.class.Status)
|
|
||||||
|
|
||||||
let existingCategories: any[] = []
|
let existingCategories: any[] = []
|
||||||
|
|
||||||
if (isStatusField) {
|
if (mixin?.grouppingManager !== undefined) {
|
||||||
existingCategories = await groupByStatusCategories(h, attrClass, categories, mgr, viewletDescriptorId)
|
const grouppingManager = await getResource(mixin.grouppingManager)
|
||||||
|
existingCategories = grouppingManager.groupByCategories(categories)
|
||||||
} else {
|
} else {
|
||||||
const valueSet = new Set<any>()
|
const valueSet = new Set<any>()
|
||||||
for (const v of categories) {
|
for (const v of categories) {
|
||||||
@ -655,56 +652,11 @@ export async function groupByCategory (
|
|||||||
return await sortCategories(h, attrClass, existingCategories, viewletDescriptorId)
|
return await sortCategories(h, attrClass, existingCategories, viewletDescriptorId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export async function groupByStatusCategories (
|
|
||||||
hierarchy: Hierarchy,
|
|
||||||
attrClass: Ref<Class<Doc>>,
|
|
||||||
categories: any[],
|
|
||||||
mgr: StatusManager,
|
|
||||||
viewletDescriptorId?: Ref<ViewletDescriptor>
|
|
||||||
): Promise<StatusValue[]> {
|
|
||||||
const existingCategories: StatusValue[] = []
|
|
||||||
const statusMap = new Map<string, StatusValue>()
|
|
||||||
|
|
||||||
const usedSpaces = new Set<Ref<Space>>()
|
|
||||||
const statusesList: Array<WithLookup<Status>> = []
|
|
||||||
for (const v of categories) {
|
|
||||||
const status = mgr.byId.get(v)
|
|
||||||
if (status !== undefined) {
|
|
||||||
statusesList.push(status)
|
|
||||||
usedSpaces.add(status.space)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const status of statusesList) {
|
|
||||||
if (status !== undefined) {
|
|
||||||
let fst = statusMap.get(status.name.toLowerCase().trim())
|
|
||||||
if (fst === undefined) {
|
|
||||||
const statuses = mgr.statuses
|
|
||||||
.filter(
|
|
||||||
(it) =>
|
|
||||||
it.ofAttribute === status.ofAttribute &&
|
|
||||||
it.name.toLowerCase().trim() === status.name.toLowerCase().trim() &&
|
|
||||||
(categories.includes(it._id) || usedSpaces.has(it.space))
|
|
||||||
)
|
|
||||||
.sort((a, b) => a.rank.localeCompare(b.rank))
|
|
||||||
fst = new StatusValue(status.name, status.color, statuses)
|
|
||||||
statusMap.set(status.name.toLowerCase().trim(), fst)
|
|
||||||
existingCategories.push(fst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return await sortCategories(hierarchy, attrClass, existingCategories, viewletDescriptorId)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCategories (
|
export async function getCategories (
|
||||||
client: TxOperations,
|
client: TxOperations,
|
||||||
_class: Ref<Class<Doc>>,
|
_class: Ref<Class<Doc>>,
|
||||||
docs: Doc[],
|
docs: Doc[],
|
||||||
key: string,
|
key: string,
|
||||||
mgr: StatusManager,
|
|
||||||
viewletDescriptorId?: Ref<ViewletDescriptor>
|
viewletDescriptorId?: Ref<ViewletDescriptor>
|
||||||
): Promise<CategoryType[]> {
|
): Promise<CategoryType[]> {
|
||||||
if (key === noCategory) return [undefined]
|
if (key === noCategory) return [undefined]
|
||||||
@ -714,7 +666,6 @@ export async function getCategories (
|
|||||||
_class,
|
_class,
|
||||||
key,
|
key,
|
||||||
docs.map((it) => getObjectValue(key, it) ?? undefined),
|
docs.map((it) => getObjectValue(key, it) ?? undefined),
|
||||||
mgr,
|
|
||||||
viewletDescriptorId
|
viewletDescriptorId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -724,7 +675,7 @@ export async function getCategories (
|
|||||||
*/
|
*/
|
||||||
export function getCategorySpaces (categories: CategoryType[]): Array<Ref<Space>> {
|
export function getCategorySpaces (categories: CategoryType[]): Array<Ref<Space>> {
|
||||||
return Array.from(
|
return Array.from(
|
||||||
(categories.filter((it) => typeof it === 'object') as StatusValue[]).reduce<Set<Ref<Space>>>((arr, val) => {
|
(categories.filter((it) => typeof it === 'object') as AggregateValue[]).reduce<Set<Ref<Space>>>((arr, val) => {
|
||||||
val.values.forEach((it) => arr.add(it.space))
|
val.values.forEach((it) => arr.add(it.space))
|
||||||
return arr
|
return arr
|
||||||
}, new Set())
|
}, new Set())
|
||||||
@ -733,12 +684,12 @@ export function getCategorySpaces (categories: CategoryType[]): Array<Ref<Space>
|
|||||||
|
|
||||||
export function concatCategories (arr1: CategoryType[], arr2: CategoryType[]): CategoryType[] {
|
export function concatCategories (arr1: CategoryType[], arr2: CategoryType[]): CategoryType[] {
|
||||||
const uniqueValues: Set<string | number | undefined> = new Set()
|
const uniqueValues: Set<string | number | undefined> = new Set()
|
||||||
const uniqueObjects: Map<string | number, StatusValue> = new Map()
|
const uniqueObjects: Map<string | number, AggregateValue> = new Map()
|
||||||
|
|
||||||
for (const item of arr1) {
|
for (const item of arr1) {
|
||||||
if (typeof item === 'object') {
|
if (typeof item === 'object') {
|
||||||
const id = item.name
|
const id = item.name
|
||||||
uniqueObjects.set(id, item)
|
uniqueObjects.set(id as any, item)
|
||||||
} else {
|
} else {
|
||||||
uniqueValues.add(item)
|
uniqueValues.add(item)
|
||||||
}
|
}
|
||||||
@ -747,8 +698,8 @@ export function concatCategories (arr1: CategoryType[], arr2: CategoryType[]): C
|
|||||||
for (const item of arr2) {
|
for (const item of arr2) {
|
||||||
if (typeof item === 'object') {
|
if (typeof item === 'object') {
|
||||||
const id = item.name
|
const id = item.name
|
||||||
if (!uniqueObjects.has(id)) {
|
if (!uniqueObjects.has(id as any)) {
|
||||||
uniqueObjects.set(id, item)
|
uniqueObjects.set(id as any, item)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uniqueValues.add(item)
|
uniqueValues.add(item)
|
||||||
|
@ -1,19 +1,10 @@
|
|||||||
import core, {
|
import { Class, Doc, DocumentQuery, Ref, SortingOrder } from '@hcengineering/core'
|
||||||
Class,
|
|
||||||
Doc,
|
|
||||||
DocumentQuery,
|
|
||||||
Ref,
|
|
||||||
SortingOrder,
|
|
||||||
Status,
|
|
||||||
StatusManager,
|
|
||||||
WithLookup,
|
|
||||||
matchQuery
|
|
||||||
} from '@hcengineering/core'
|
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import { LiveQuery, createQuery, getAttributePresenterClass, getClient } from '@hcengineering/presentation'
|
import { LiveQuery, createQuery, getAttributePresenterClass, getClient } from '@hcengineering/presentation'
|
||||||
import { locationToUrl, getCurrentResolvedLocation } from '@hcengineering/ui'
|
import { locationToUrl, getCurrentResolvedLocation } from '@hcengineering/ui'
|
||||||
import {
|
import {
|
||||||
DropdownViewOption,
|
DropdownViewOption,
|
||||||
|
Groupping,
|
||||||
ToggleViewOption,
|
ToggleViewOption,
|
||||||
ViewOptionModel,
|
ViewOptionModel,
|
||||||
ViewOptions,
|
ViewOptions,
|
||||||
@ -118,7 +109,6 @@ export async function showEmptyGroups (
|
|||||||
key: string,
|
key: string,
|
||||||
onUpdate: () => void,
|
onUpdate: () => void,
|
||||||
queryId: Ref<Doc>,
|
queryId: Ref<Doc>,
|
||||||
mgr: StatusManager,
|
|
||||||
viewletDescriptorId?: Ref<ViewletDescriptor>
|
viewletDescriptorId?: Ref<ViewletDescriptor>
|
||||||
): Promise<any[] | undefined> {
|
): Promise<any[] | undefined> {
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
@ -129,32 +119,27 @@ export async function showEmptyGroups (
|
|||||||
const { attrClass } = getAttributePresenterClass(hierarchy, attr)
|
const { attrClass } = getAttributePresenterClass(hierarchy, attr)
|
||||||
const attributeClass = hierarchy.getClass(attrClass)
|
const attributeClass = hierarchy.getClass(attrClass)
|
||||||
|
|
||||||
if (hierarchy.isDerived(attrClass, core.class.Status)) {
|
let groupMixin: Groupping | undefined
|
||||||
// We do not need extensions for all status categories.
|
if (hierarchy.hasMixin(attributeClass, view.mixin.Groupping)) {
|
||||||
let statusList = mgr.filter((it) => {
|
groupMixin = hierarchy.as(attributeClass, view.mixin.Groupping)
|
||||||
return it.ofAttribute === attr._id
|
} else {
|
||||||
})
|
const _attributeClass = hierarchy.classHierarchyMixin(attrClass, view.mixin.Groupping)
|
||||||
if (query !== undefined) {
|
if (_attributeClass !== undefined) {
|
||||||
const { [key]: st, space } = query
|
groupMixin = hierarchy.as(_attributeClass, view.mixin.Groupping)
|
||||||
const resQuery: DocumentQuery<Status> = {}
|
|
||||||
if (space !== undefined) {
|
|
||||||
resQuery.space = space
|
|
||||||
}
|
}
|
||||||
if (st !== undefined) {
|
|
||||||
resQuery._id = st
|
|
||||||
}
|
}
|
||||||
statusList = matchQuery<Status>(statusList, resQuery, _class, hierarchy) as unknown as Array<WithLookup<Status>>
|
if (groupMixin?.grouppingManager !== undefined) {
|
||||||
}
|
const grouppingManager = await getResource(groupMixin.grouppingManager)
|
||||||
const statuses = statusList.map((it) => it._id)
|
const docs = grouppingManager.groupValuesWithEmpty(hierarchy, _class, key, query)
|
||||||
return await groupByCategory(client, _class, key, statuses, mgr, viewletDescriptorId)
|
return await groupByCategory(client, _class, key, docs, viewletDescriptorId)
|
||||||
}
|
}
|
||||||
|
|
||||||
const mixin = hierarchy.as(attributeClass, view.mixin.AllValuesFunc)
|
const allValuesMixin = hierarchy.as(attributeClass, view.mixin.AllValuesFunc)
|
||||||
if (mixin.func !== undefined) {
|
if (allValuesMixin.func !== undefined) {
|
||||||
const f = await getResource(mixin.func)
|
const f = await getResource(allValuesMixin.func)
|
||||||
const res = await f(query, onUpdate, queryId)
|
const res = await f(query, onUpdate, queryId)
|
||||||
if (res !== undefined) {
|
if (res !== undefined) {
|
||||||
return await groupByCategory(client, _class, key, res, mgr, viewletDescriptorId)
|
return await groupByCategory(client, _class, key, res, viewletDescriptorId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,15 +14,18 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type {
|
import {
|
||||||
Account,
|
Account,
|
||||||
|
AggregateValue,
|
||||||
AnyAttribute,
|
AnyAttribute,
|
||||||
|
Attribute,
|
||||||
CategoryType,
|
CategoryType,
|
||||||
Class,
|
Class,
|
||||||
Client,
|
Client,
|
||||||
Doc,
|
Doc,
|
||||||
DocumentQuery,
|
DocumentQuery,
|
||||||
FindOptions,
|
FindOptions,
|
||||||
|
Hierarchy,
|
||||||
Lookup,
|
Lookup,
|
||||||
Mixin,
|
Mixin,
|
||||||
Obj,
|
Obj,
|
||||||
@ -31,14 +34,15 @@ import type {
|
|||||||
Ref,
|
Ref,
|
||||||
SortingOrder,
|
SortingOrder,
|
||||||
Space,
|
Space,
|
||||||
StatusManager,
|
|
||||||
StatusValue,
|
StatusValue,
|
||||||
|
Tx,
|
||||||
Type,
|
Type,
|
||||||
UXObject
|
UXObject,
|
||||||
|
WithLookup
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { Asset, IntlString, Plugin, Resource, Status, plugin } from '@hcengineering/platform'
|
import { Asset, IntlString, Plugin, Resource, Status, plugin } from '@hcengineering/platform'
|
||||||
import type { Preference } from '@hcengineering/preference'
|
import { Preference } from '@hcengineering/preference'
|
||||||
import type {
|
import {
|
||||||
AnyComponent,
|
AnyComponent,
|
||||||
AnySvelteComponent,
|
AnySvelteComponent,
|
||||||
Location,
|
Location,
|
||||||
@ -308,6 +312,62 @@ export interface AllValuesFunc extends Class<Doc> {
|
|||||||
func: GetAllValuesFunc
|
func: GetAllValuesFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface GrouppingManager {
|
||||||
|
groupByCategories: (categories: any[]) => AggregateValue[]
|
||||||
|
groupValues: (val: Doc[], targets: Set<any>) => Doc[]
|
||||||
|
groupValuesWithEmpty: (
|
||||||
|
hierarchy: Hierarchy,
|
||||||
|
_class: Ref<Class<Doc>>,
|
||||||
|
key: string,
|
||||||
|
query: DocumentQuery<Doc> | undefined
|
||||||
|
) => Array<Ref<Doc>>
|
||||||
|
hasValue: (value: Doc | undefined | null, values: any[]) => boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type GrouppingManagerResource = Resource<GrouppingManager>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface Groupping extends Class<Doc> {
|
||||||
|
grouppingManager: GrouppingManagerResource
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface AggregationManager {
|
||||||
|
close: () => void
|
||||||
|
notifyTx: (tx: Tx) => Promise<void>
|
||||||
|
updateLookup: (resultDoc: WithLookup<Doc>, attr: Attribute<Doc>) => 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>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type AggregationManagerResource = Resource<AggregationManager>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type CreateAggregationManagerFunc = Resource<(client: Client, lqCallback: () => void) => AggregationManager>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface Aggregation extends Class<Doc> {
|
||||||
|
createAggregationManager: CreateAggregationManagerFunc
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -578,7 +638,6 @@ export type ViewCategoryActionFunc = (
|
|||||||
key: string,
|
key: string,
|
||||||
onUpdate: () => void,
|
onUpdate: () => void,
|
||||||
queryId: Ref<Doc>,
|
queryId: Ref<Doc>,
|
||||||
mgr: StatusManager,
|
|
||||||
viewletDescriptorId?: Ref<ViewletDescriptor>
|
viewletDescriptorId?: Ref<ViewletDescriptor>
|
||||||
) => Promise<CategoryType[] | undefined>
|
) => Promise<CategoryType[] | undefined>
|
||||||
/**
|
/**
|
||||||
@ -690,7 +749,9 @@ const view = plugin(viewId, {
|
|||||||
ObjectPanel: '' as Ref<Mixin<ObjectPanel>>,
|
ObjectPanel: '' as Ref<Mixin<ObjectPanel>>,
|
||||||
LinkProvider: '' as Ref<Mixin<LinkProvider>>,
|
LinkProvider: '' as Ref<Mixin<LinkProvider>>,
|
||||||
SpacePresenter: '' as Ref<Mixin<SpacePresenter>>,
|
SpacePresenter: '' as Ref<Mixin<SpacePresenter>>,
|
||||||
AttributeFilterPresenter: '' as Ref<Mixin<AttributeFilterPresenter>>
|
AttributeFilterPresenter: '' as Ref<Mixin<AttributeFilterPresenter>>,
|
||||||
|
Aggregation: '' as Ref<Mixin<Aggregation>>,
|
||||||
|
Groupping: '' as Ref<Mixin<Groupping>>
|
||||||
},
|
},
|
||||||
class: {
|
class: {
|
||||||
ViewletPreference: '' as Ref<Class<ViewletPreference>>,
|
ViewletPreference: '' as Ref<Class<ViewletPreference>>,
|
||||||
|
Loading…
Reference in New Issue
Block a user