Objects sort (#672)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2021-12-20 15:06:31 +06:00 committed by GitHub
parent 9a8d9f8804
commit 81000b50f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 97 additions and 50 deletions

View File

@ -15,7 +15,7 @@
import type { Domain, Type, Ref } from '@anticrm/core' import type { Domain, Type, Ref } from '@anticrm/core'
import { DOMAIN_MODEL, IndexKind } from '@anticrm/core' import { DOMAIN_MODEL, IndexKind } from '@anticrm/core'
import { Builder, Model, Prop, TypeString, UX, Index, Collection } from '@anticrm/model' import { Builder, Model, Prop, TypeString, UX, Index, Collection, ArrOf } from '@anticrm/model'
import type { IntlString, Asset } from '@anticrm/platform' import type { IntlString, Asset } from '@anticrm/platform'
import chunter from '@anticrm/model-chunter' import chunter from '@anticrm/model-chunter'
import core, { TAccount, TDoc, TSpace, TType } from '@anticrm/model-core' import core, { TAccount, TDoc, TSpace, TType } from '@anticrm/model-core'
@ -44,14 +44,14 @@ export class TChannelProvider extends TDoc implements ChannelProvider {
placeholder!: string placeholder!: string
} }
@Model(contact.class.TypeChannels, core.class.Type) @Model(contact.class.TypeChannel, core.class.Type)
export class TTypeChannels extends TType {} export class TTypeChannels extends TType {}
/** /**
* @public * @public
*/ */
export function TypeChannels (): Type<Channel[]> { export function TypeChannel (): Type<Channel> {
return { _class: contact.class.TypeChannels, label: 'TypeChannels' as IntlString } return { _class: contact.class.TypeChannel, label: 'Channel' as IntlString }
} }
@Model(contact.class.Contact, core.class.Doc, DOMAIN_CONTACT) @Model(contact.class.Contact, core.class.Doc, DOMAIN_CONTACT)
@ -62,7 +62,7 @@ export class TContact extends TDoc implements Contact {
avatar?: string avatar?: string
@Prop(TypeChannels(), 'Contact Info' as IntlString) @Prop(ArrOf(TypeChannel()), 'Contact Info' as IntlString)
channels!: Channel[] channels!: Channel[]
@Prop(Collection(attachment.class.Attachment), 'Attachments' as IntlString) @Prop(Collection(attachment.class.Attachment), 'Attachments' as IntlString)
@ -73,14 +73,14 @@ export class TContact extends TDoc implements Contact {
} }
@Model(contact.class.Person, contact.class.Contact) @Model(contact.class.Person, contact.class.Contact)
@UX('Person' as IntlString, contact.icon.Person) @UX('Person' as IntlString, contact.icon.Person, undefined, 'name')
export class TPerson extends TContact implements Person { export class TPerson extends TContact implements Person {
@Prop(TypeString(), 'City' as IntlString) @Prop(TypeString(), 'City' as IntlString)
city!: string city!: string
} }
@Model(contact.class.Organization, contact.class.Contact) @Model(contact.class.Organization, contact.class.Contact)
@UX('Organization' as IntlString, contact.icon.Company) @UX('Organization' as IntlString, contact.icon.Company, undefined, 'name')
export class TOrganization extends TContact implements Organization {} export class TOrganization extends TContact implements Organization {}
@Model(contact.class.Employee, contact.class.Person) @Model(contact.class.Employee, contact.class.Person)
@ -158,7 +158,7 @@ export function createModel (builder: Builder): void {
config: [ config: [
'', '',
'city', 'city',
{ presenter: attachment.component.AttachmentsPresenter, label: 'Files' }, { presenter: attachment.component.AttachmentsPresenter, label: 'Files', sortingKey: 'attachments' },
'modifiedOn', 'modifiedOn',
'channels' 'channels'
] ]
@ -170,7 +170,7 @@ export function createModel (builder: Builder): void {
open: contact.component.EditContact, open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {}, options: {},
config: ['', { presenter: attachment.component.AttachmentsPresenter, label: 'Files' }, 'modifiedOn', 'channels'] config: ['', { presenter: attachment.component.AttachmentsPresenter, label: 'Files', sortingKey: 'attachments' }, 'modifiedOn', 'channels']
}) })
builder.mixin(contact.class.Person, core.class.Class, view.mixin.ObjectEditor, { builder.mixin(contact.class.Person, core.class.Class, view.mixin.ObjectEditor, {
@ -185,7 +185,7 @@ export function createModel (builder: Builder): void {
editor: contact.component.EditOrganization editor: contact.component.EditOrganization
}) })
builder.mixin(contact.class.TypeChannels, core.class.Class, view.mixin.AttributePresenter, { builder.mixin(contact.class.TypeChannel, core.class.Class, view.mixin.AttributePresenter, {
presenter: contact.component.ChannelsPresenter presenter: contact.component.ChannelsPresenter
}) })

View File

@ -44,6 +44,6 @@ export const ids = mergeIds(contactId, contact, {
CreateOrganizations: '' as IntlString CreateOrganizations: '' as IntlString
}, },
class: { class: {
TypeChannels: '' as Ref<Class<Type<Channel[]>>> TypeChannel: '' as Ref<Class<Type<Channel>>>
} }
}) })

View File

@ -36,7 +36,7 @@ import lead from './plugin'
export class TFunnel extends TSpaceWithStates implements Funnel {} export class TFunnel extends TSpaceWithStates implements Funnel {}
@Model(lead.class.Lead, task.class.Task) @Model(lead.class.Lead, task.class.Task)
@UX('Lead' as IntlString, lead.icon.Lead) @UX('Lead' as IntlString, lead.icon.Lead, undefined, 'title')
export class TLead extends TTask implements Lead { export class TLead extends TTask implements Lead {
@Prop(TypeString(), 'Title' as IntlString) @Prop(TypeString(), 'Title' as IntlString)
title!: string title!: string
@ -108,8 +108,8 @@ export function createModel (builder: Builder): void {
'', '',
'$lookup.customer', '$lookup.customer',
'$lookup.state', '$lookup.state',
{ presenter: attachment.component.AttachmentsPresenter, label: 'Files' }, { presenter: attachment.component.AttachmentsPresenter, label: 'Files', sortingKey: 'attachments' },
{ presenter: chunter.component.CommentsPresenter, label: 'Comments' }, { presenter: chunter.component.CommentsPresenter, label: 'Comments', sortingKey: 'comments' },
'modifiedOn', 'modifiedOn',
'$lookup.customer.channels' '$lookup.customer.channels'
] ]

View File

@ -70,7 +70,7 @@ export class TCandidate extends TPerson implements Candidate {
} }
@Model(recruit.class.Applicant, task.class.Task) @Model(recruit.class.Applicant, task.class.Task)
@UX('Application' as IntlString, recruit.icon.Application, 'APP' as IntlString) @UX('Application' as IntlString, recruit.icon.Application, 'APP' as IntlString, 'number')
export class TApplicant extends TTask implements Applicant { export class TApplicant extends TTask implements Applicant {
// We need to declare, to provide property with label // We need to declare, to provide property with label
@Prop(TypeRef(recruit.class.Candidate), 'Candidate' as IntlString) @Prop(TypeRef(recruit.class.Candidate), 'Candidate' as IntlString)
@ -160,9 +160,9 @@ export function createModel (builder: Builder): void {
'', '',
'title', 'title',
'city', 'city',
{ presenter: recruit.component.ApplicationsPresenter, label: 'Apps' }, { presenter: recruit.component.ApplicationsPresenter, label: 'Apps', sortingKey: 'applications' },
{ presenter: attachment.component.AttachmentsPresenter, label: 'Files' }, { presenter: attachment.component.AttachmentsPresenter, label: 'Files', sortingKey: 'attachments' },
{ presenter: chunter.component.CommentsPresenter, label: 'Comments' }, { presenter: chunter.component.CommentsPresenter, label: 'Comments', sortingKey: 'comments' },
'modifiedOn', 'modifiedOn',
'channels' 'channels'
] ]
@ -187,9 +187,8 @@ export function createModel (builder: Builder): void {
'$lookup.assignee', '$lookup.assignee',
'$lookup.state', '$lookup.state',
'$lookup.doneState', '$lookup.doneState',
// '$lookup.attachedTo.city', { presenter: attachment.component.AttachmentsPresenter, label: 'Files', sortingKey: 'attachments' },
{ presenter: attachment.component.AttachmentsPresenter, label: 'Files' }, { presenter: chunter.component.CommentsPresenter, label: 'Comments', sortingKey: 'comments' },
{ presenter: chunter.component.CommentsPresenter, label: 'Comments' },
'modifiedOn', 'modifiedOn',
'$lookup.attachedTo.channels' '$lookup.attachedTo.channels'
] ]

View File

@ -54,6 +54,7 @@ export const DOMAIN_TASK = 'task' as Domain
export const DOMAIN_STATE = 'state' as Domain export const DOMAIN_STATE = 'state' as Domain
export const DOMAIN_KANBAN = 'kanban' as Domain export const DOMAIN_KANBAN = 'kanban' as Domain
@Model(task.class.State, core.class.Doc, DOMAIN_STATE, [core.interface.DocWithRank]) @Model(task.class.State, core.class.Doc, DOMAIN_STATE, [core.interface.DocWithRank])
@UX('State' as IntlString, undefined, undefined, 'title')
export class TState extends TDoc implements State { export class TState extends TDoc implements State {
@Prop(TypeString(), 'Title' as IntlString) @Prop(TypeString(), 'Title' as IntlString)
title!: string title!: string
@ -64,6 +65,7 @@ export class TState extends TDoc implements State {
} }
@Model(task.class.DoneState, core.class.Doc, DOMAIN_STATE, [core.interface.DocWithRank]) @Model(task.class.DoneState, core.class.Doc, DOMAIN_STATE, [core.interface.DocWithRank])
@UX('Done' as IntlString, undefined, undefined, 'title')
export class TDoneState extends TDoc implements DoneState { export class TDoneState extends TDoc implements DoneState {
@Prop(TypeString(), 'Title' as IntlString) @Prop(TypeString(), 'Title' as IntlString)
title!: string title!: string
@ -107,7 +109,7 @@ export class TSpaceWithStates extends TSpace {}
export class TProject extends TSpaceWithStates implements Project {} export class TProject extends TSpaceWithStates implements Project {}
@Model(task.class.Issue, task.class.Task, DOMAIN_TASK) @Model(task.class.Issue, task.class.Task, DOMAIN_TASK)
@UX('Task' as IntlString, task.icon.Task, 'Task' as IntlString) @UX('Task' as IntlString, task.icon.Task, 'Task' as IntlString, 'number')
export class TIssue extends TTask implements Issue { export class TIssue extends TTask implements Issue {
// We need to declare, to provide property with label // We need to declare, to provide property with label
@Prop(TypeRef(core.class.Doc), 'Parent' as IntlString) @Prop(TypeRef(core.class.Doc), 'Parent' as IntlString)
@ -254,8 +256,8 @@ export function createModel (builder: Builder): void {
'', '',
'name', 'name',
'$lookup.assignee', '$lookup.assignee',
{ presenter: attachment.component.AttachmentsPresenter, label: 'Files' }, { presenter: attachment.component.AttachmentsPresenter, label: 'Files', sortingKey: 'attachments' },
{ presenter: chunter.component.CommentsPresenter, label: 'Comments' }, { presenter: chunter.component.CommentsPresenter, label: 'Comments', sortingKey: 'comments' },
'modifiedOn' 'modifiedOn'
] ]
}) })

View File

@ -15,17 +15,21 @@
// //
import type { IntlString } from '@anticrm/platform' import type { IntlString } from '@anticrm/platform'
import { Builder, Model, TypeString, TypeBoolean, Prop, Collection } from '@anticrm/model' import { Builder, Model, TypeString, TypeBoolean, Prop, ArrOf } from '@anticrm/model'
import core, { TAttachedDoc, TDoc } from '@anticrm/model-core' import core, { TAttachedDoc, TDoc } from '@anticrm/model-core'
import contact from '@anticrm/model-contact' import contact from '@anticrm/model-contact'
import telegram from './plugin' import telegram from './plugin'
import type { TelegramMessage, SharedTelegramMessage, SharedTelegramMessages } from '@anticrm/telegram' import type { TelegramMessage, SharedTelegramMessage, SharedTelegramMessages } from '@anticrm/telegram'
import type { Domain } from '@anticrm/core' import type { Domain, Type } from '@anticrm/core'
import setting from '@anticrm/setting' import setting from '@anticrm/setting'
import activity from '@anticrm/activity' import activity from '@anticrm/activity'
export const DOMAIN_TELEGRAM = 'telegram' as Domain export const DOMAIN_TELEGRAM = 'telegram' as Domain
function TypeSharedMessage (): Type<SharedTelegramMessage> {
return { _class: telegram.class.SharedMessage, label: 'Shared message' as IntlString }
}
@Model(telegram.class.Message, core.class.Doc, DOMAIN_TELEGRAM) @Model(telegram.class.Message, core.class.Doc, DOMAIN_TELEGRAM)
export class TTelegramMessage extends TDoc implements TelegramMessage { export class TTelegramMessage extends TDoc implements TelegramMessage {
@Prop(TypeString(), 'Content' as IntlString) @Prop(TypeString(), 'Content' as IntlString)
@ -43,7 +47,7 @@ export class TTelegramMessage extends TDoc implements TelegramMessage {
@Model(telegram.class.SharedMessages, core.class.AttachedDoc, DOMAIN_TELEGRAM) @Model(telegram.class.SharedMessages, core.class.AttachedDoc, DOMAIN_TELEGRAM)
export class TSharedTelegramMessages extends TAttachedDoc implements SharedTelegramMessages { export class TSharedTelegramMessages extends TAttachedDoc implements SharedTelegramMessages {
@Prop(Collection(telegram.class.SharedMessage), 'Messages' as IntlString) @Prop(ArrOf(TypeSharedMessage()), 'Messages' as IntlString)
messages!: SharedTelegramMessage[] messages!: SharedTelegramMessage[]
} }

View File

@ -136,6 +136,7 @@ export interface Class<T extends Obj> extends Classifier {
implements?: Ref<Interface<Doc>>[] implements?: Ref<Interface<Doc>>[]
domain?: Domain domain?: Domain
shortLabel?: IntlString shortLabel?: IntlString
sortingKey?: string
} }
/** /**

View File

@ -14,7 +14,7 @@
// //
import type { Plugin, StatusCode } from '@anticrm/platform' import type { Plugin, StatusCode } from '@anticrm/platform'
import { plugin } from '@anticrm/platform' import { plugin } from '@anticrm/platform'
import type { Account, AnyAttribute, AttachedDoc, DocWithRank, Class, Doc, Interface, Obj, PropertyType, Ref, Space, Timestamp, Type, Collection, RefTo } from './classes' import type { Account, ArrOf, AnyAttribute, AttachedDoc, DocWithRank, Class, Doc, Interface, Obj, PropertyType, Ref, Space, Timestamp, Type, Collection, RefTo } from './classes'
import type { Tx, TxBulkWrite, TxCollectionCUD, TxCreateDoc, TxCUD, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx' import type { Tx, TxBulkWrite, TxCollectionCUD, TxCreateDoc, TxCUD, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
/** /**
@ -46,6 +46,7 @@ export default plugin(coreId, {
TypeTimestamp: '' as Ref<Class<Type<Timestamp>>>, TypeTimestamp: '' as Ref<Class<Type<Timestamp>>>,
TypeDate: '' as Ref<Class<Type<Timestamp | Date>>>, TypeDate: '' as Ref<Class<Type<Timestamp | Date>>>,
RefTo: '' as Ref<Class<RefTo<Doc>>>, RefTo: '' as Ref<Class<RefTo<Doc>>>,
ArrOf: '' as Ref<Class<ArrOf<Doc>>>,
Collection: '' as Ref<Class<Collection<AttachedDoc>>>, Collection: '' as Ref<Class<Collection<AttachedDoc>>>,
Bag: '' as Ref<Class<Type<Record<string, PropertyType>>>> Bag: '' as Ref<Class<Type<Record<string, PropertyType>>>>
}, },

View File

@ -75,18 +75,30 @@ function arrayOrValue (vv: any): any[] {
export function resultSort<T extends Doc> (result: T[], sortOptions: SortingQuery<T>): void { export function resultSort<T extends Doc> (result: T[], sortOptions: SortingQuery<T>): void {
const sortFunc = (a: any, b: any): number => { const sortFunc = (a: any, b: any): number => {
for (const key in sortOptions) { for (const key in sortOptions) {
let aValue = getNestedValue(key, a) const aValue = getValue(key, a)
if (typeof aValue === 'object') { const bValue = getValue(key, b)
aValue = JSON.stringify(aValue) const result = getSortingResult(aValue, bValue)
}
let bValue = getNestedValue(key, b)
if (typeof bValue === 'object') {
bValue = JSON.stringify(bValue)
}
const result = typeof aValue === 'string' ? aValue.localeCompare(bValue) : aValue - bValue
if (result !== 0) return result * (sortOptions[key] as number) if (result !== 0) return result * (sortOptions[key] as number)
} }
return 0 return 0
} }
result.sort(sortFunc) result.sort(sortFunc)
} }
function getSortingResult (aValue: any, bValue: any): number {
if (typeof aValue === 'undefined') {
return typeof bValue === 'undefined' ? 0 : -1
}
if (typeof bValue === 'undefined') {
return 1
}
return typeof aValue === 'string' ? aValue.localeCompare(bValue) : (aValue - bValue)
}
function getValue (key: string, obj: any): any {
let value = getNestedValue(key, obj)
if (typeof value === 'object') {
value = JSON.stringify(value)
}
return value
}

View File

@ -14,6 +14,7 @@
// //
import core, { import core, {
ArrOf as TypeArrOf,
Account, Account,
AttachedDoc, Collection as TypeCollection, RefTo, AttachedDoc, Collection as TypeCollection, RefTo,
Attribute, Class, Classifier, ClassifierKind, Data, Doc, Domain, ExtendedAttributes, generateId, IndexKind, Interface, Mixin as IMixin, Obj, PropertyType, Ref, Space, Tx, TxCreateDoc, TxFactory, TxProcessor, Type Attribute, Class, Classifier, ClassifierKind, Data, Doc, Domain, ExtendedAttributes, generateId, IndexKind, Interface, Mixin as IMixin, Obj, PropertyType, Ref, Space, Tx, TxCreateDoc, TxFactory, TxProcessor, Type
@ -48,6 +49,7 @@ interface ClassTxes {
txes: Array<NoIDs<Tx>> txes: Array<NoIDs<Tx>>
kind: ClassifierKind kind: ClassifierKind
shortLabel?: IntlString shortLabel?: IntlString
sortingKey?: string
} }
const transactions = new Map<any, ClassTxes>() const transactions = new Map<any, ClassTxes>()
@ -159,13 +161,15 @@ export function Mixin<T extends Obj> (
export function UX<T extends Obj> ( export function UX<T extends Obj> (
label: IntlString, label: IntlString,
icon?: Asset, icon?: Asset,
shortLabel?: IntlString shortLabel?: IntlString,
sortingKey?: string
) { ) {
return function classDecorator<C extends new () => T> (constructor: C): void { return function classDecorator<C extends new () => T> (constructor: C): void {
const txes = getTxes(constructor.prototype) const txes = getTxes(constructor.prototype)
txes.label = label txes.label = label
txes.icon = icon txes.icon = icon
txes.shortLabel = shortLabel txes.shortLabel = shortLabel
txes.sortingKey = sortingKey
} }
} }
@ -194,7 +198,8 @@ function _generateTx (tx: ClassTxes): Tx[] {
...(tx.kind === ClassifierKind.INTERFACE ? { extends: tx.implements } : { extends: tx.extends, implements: tx.implements }), ...(tx.kind === ClassifierKind.INTERFACE ? { extends: tx.implements } : { extends: tx.extends, implements: tx.implements }),
label: tx.label, label: tx.label,
icon: tx.icon, icon: tx.icon,
shortLabel: tx.shortLabel shortLabel: tx.shortLabel,
sortingKey: tx.sortingKey
}, },
objectId objectId
) )
@ -320,6 +325,13 @@ export function Collection<T extends AttachedDoc> (clazz: Ref<Class<T>>): TypeCo
return { _class: core.class.Collection, of: clazz, label: 'Collection' as IntlString } return { _class: core.class.Collection, of: clazz, label: 'Collection' as IntlString }
} }
/**
* @public
*/
export function ArrOf<T extends PropertyType> (type: Type<T>): TypeArrOf<T> {
return { _class: core.class.ArrOf, of: type, label: 'Array' as IntlString }
}
/** /**
* @public * @public
*/ */

View File

@ -32,7 +32,8 @@ import core, {
AnyAttribute, AnyAttribute,
RefTo, RefTo,
Collection, Collection,
AttachedDoc AttachedDoc,
ArrOf
} from '@anticrm/core' } from '@anticrm/core'
import { LiveQuery as LQ } from '@anticrm/query' import { LiveQuery as LQ } from '@anticrm/query'
import { getMetadata } from '@anticrm/platform' import { getMetadata } from '@anticrm/platform'
@ -120,5 +121,8 @@ export function getAttributePresenterClass (attribute: AnyAttribute): Ref<Class<
if (attrClass === core.class.Collection) { if (attrClass === core.class.Collection) {
attrClass = (attribute.type as Collection<AttachedDoc>).of attrClass = (attribute.type as Collection<AttachedDoc>).of
} }
if (attrClass === core.class.ArrOf) {
attrClass = (attribute.type as ArrOf<AttachedDoc>).of._class
}
return attrClass return attrClass
} }

View File

@ -76,10 +76,10 @@
<thead> <thead>
<tr class="tr-head"> <tr class="tr-head">
{#each model as attribute} {#each model as attribute}
<th class:sortable={attribute.key} class:sorted={attribute.key === sortKey} on:click={() => changeSorting(attribute.key)}> <th class:sortable={attribute.sortingKey} class:sorted={attribute.sortingKey === sortKey} on:click={() => changeSorting(attribute.sortingKey)}>
<div class="flex-row-center whitespace-nowrap"> <div class="flex-row-center whitespace-nowrap">
<Label label = {attribute.label}/> <Label label = {attribute.label}/>
{#if attribute.key === sortKey} {#if attribute.sortingKey === sortKey}
<div class="icon"> <div class="icon">
{#if sortOrder === SortingOrder.Ascending} {#if sortOrder === SortingOrder.Ascending}
<IconUp size={'small'} /> <IconUp size={'small'} />

View File

@ -115,13 +115,13 @@
</th> </th>
{/if} {/if}
<th <th
class:sortable={attribute.key} class:sortable={attribute.sortingKey}
class:sorted={attribute.key === sortKey} class:sorted={attribute.sortingKey === sortKey}
on:click={() => changeSorting(attribute.key)} on:click={() => changeSorting(attribute.sortingKey)}
> >
<div class="flex-row-center whitespace-nowrap"> <div class="flex-row-center whitespace-nowrap">
<Label label={attribute.label} /> <Label label={attribute.label} />
{#if attribute.key === sortKey} {#if attribute.sortingKey === sortKey}
<div class="icon"> <div class="icon">
{#if sortOrder === SortingOrder.Ascending} {#if sortOrder === SortingOrder.Ascending}
<IconUp size={'small'} /> <IconUp size={'small'} />

View File

@ -36,11 +36,16 @@ export async function getObjectPresenter (client: Client, _class: Ref<Class<Obj>
} }
} }
const presenter = await getResource(presenterMixin.presenter) const presenter = await getResource(presenterMixin.presenter)
const key = typeof preserveKey === 'string' ? preserveKey : ''
const sortingKey = clazz.sortingKey ?
(key.length > 0 ? key + '.' + clazz.sortingKey : clazz.sortingKey)
: key
return { return {
key: typeof preserveKey === 'string' ? preserveKey : '', key,
_class, _class,
label: clazz.label, label: clazz.label,
presenter presenter,
sortingKey
} }
} }
@ -59,9 +64,12 @@ async function getAttributePresenter (client: Client, _class: Ref<Class<Obj>>, k
if (presenterMixin.presenter === undefined) { if (presenterMixin.presenter === undefined) {
throw new Error('attribute presenter not found for ' + JSON.stringify(preserveKey)) throw new Error('attribute presenter not found for ' + JSON.stringify(preserveKey))
} }
const resultKey = typeof preserveKey === 'string' ? preserveKey : ''
const sortingKey = attribute.type._class === core.class.ArrOf ? resultKey + '.length' : resultKey
const presenter = await getResource(presenterMixin.presenter) const presenter = await getResource(presenterMixin.presenter)
return { return {
key: typeof preserveKey === 'string' ? preserveKey : '', key: resultKey,
sortingKey,
_class: attrClass, _class: attrClass,
label: attribute.label, label: attribute.label,
presenter presenter
@ -70,9 +78,10 @@ async function getAttributePresenter (client: Client, _class: Ref<Class<Obj>>, k
async function getPresenter (client: Client, _class: Ref<Class<Obj>>, key: BuildModelKey, preserveKey: BuildModelKey, options?: FindOptions<Doc>): Promise<AttributeModel> { async function getPresenter (client: Client, _class: Ref<Class<Obj>>, key: BuildModelKey, preserveKey: BuildModelKey, options?: FindOptions<Doc>): Promise<AttributeModel> {
if (typeof key === 'object') { if (typeof key === 'object') {
const { presenter, label } = key const { presenter, label, sortingKey } = key
return { return {
key: '', key: '',
sortingKey: sortingKey ?? '',
_class, _class,
label: label as IntlString, label: label as IntlString,
presenter: await getResource(presenter) presenter: await getResource(presenter)
@ -116,6 +125,7 @@ export async function buildModel (options: BuildModelOptions): Promise<Attribute
console.error('Failed to find presenter for', key, err) console.error('Failed to find presenter for', key, err)
const errorPresenter: AttributeModel = { const errorPresenter: AttributeModel = {
key: '', key: '',
sortingKey: '',
presenter: ErrorPresenter, presenter: ErrorPresenter,
label: stringKey as IntlString, label: stringKey as IntlString,
_class: core.class.TypeString, _class: core.class.TypeString,

View File

@ -84,6 +84,7 @@ export const viewId = 'view' as Plugin
export type BuildModelKey = string | { export type BuildModelKey = string | {
presenter: AnyComponent presenter: AnyComponent
label: string label: string
sortingKey?: string
} }
/** /**
@ -96,6 +97,7 @@ export interface AttributeModel {
presenter: AnySvelteComponent presenter: AnySvelteComponent
// Extra properties for component // Extra properties for component
props?: Record<string, any> props?: Record<string, any>
sortingKey: string
} }
/** /**