Add list item presenter (#2449)

Signed-off-by: Sergei Ogorelkov <sergei.ogorelkov@xored.com>
This commit is contained in:
Sergei Ogorelkov 2022-12-20 09:26:51 +06:00 committed by GitHub
parent 0718ca0812
commit bf550d067f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 19 deletions

View File

@ -44,6 +44,7 @@ import type {
ObjectTitle, ObjectTitle,
ObjectValidator, ObjectValidator,
PreviewPresenter, PreviewPresenter,
ListItemPresenter,
SpaceHeader, SpaceHeader,
SpaceName, SpaceName,
ViewAction, ViewAction,
@ -133,6 +134,11 @@ export class TAttributePresenter extends TClass implements AttributePresenter {
presenter!: AnyComponent presenter!: AnyComponent
} }
@Mixin(view.mixin.ListItemPresenter, core.class.Class)
export class TListItemPresenter extends TClass implements ListItemPresenter {
presenter!: AnyComponent
}
@Mixin(view.mixin.ObjectEditor, core.class.Class) @Mixin(view.mixin.ObjectEditor, core.class.Class)
export class TObjectEditor extends TClass implements ObjectEditor { export class TObjectEditor extends TClass implements ObjectEditor {
editor!: AnyComponent editor!: AnyComponent
@ -269,6 +275,7 @@ export function createModel (builder: Builder): void {
TAttributeFilter, TAttributeFilter,
TAttributeEditor, TAttributeEditor,
TAttributePresenter, TAttributePresenter,
TListItemPresenter,
TCollectionEditor, TCollectionEditor,
TCollectionPresenter, TCollectionPresenter,
TObjectEditor, TObjectEditor,

View File

@ -17,16 +17,17 @@
import { getResource, IntlString } from '@hcengineering/platform' import { getResource, IntlString } from '@hcengineering/platform'
import presentation, { createQuery, getClient } from '@hcengineering/presentation' import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { calcRank, DocWithRank } from '@hcengineering/task' import { calcRank, DocWithRank } from '@hcengineering/task'
import { AnyComponent, Button, Component, IconAdd, Label, Loading } from '@hcengineering/ui' import { Button, Component, IconAdd, Label, Loading } from '@hcengineering/ui'
import view, { AttributeModel, ObjectFactory } from '@hcengineering/view' import view, { ObjectFactory } from '@hcengineering/view'
import { flip } from 'svelte/animate' import { flip } from 'svelte/animate'
import { getObjectPresenter } from '../../utils' import { SvelteComponentDev } from 'svelte/internal'
import { getListItemPresenter, getObjectPresenter } from '../../utils'
/* /*
How to use: How to use:
We must add presenter for the "_class" via "AttributePresenter" mixin We must add presenter for the "_class" via "ListItemPresenter" / "AttributePresenter"
or pass it through "presenter" prop to be able display the rows list. mixins or render the "object" slot to be able display the rows list.
To create a new items, we should add "ObjectFactory" mixin also. To create a new items, we should add "ObjectFactory" mixin also.
@ -39,7 +40,6 @@
export let label: IntlString | undefined = undefined export let label: IntlString | undefined = undefined
export let query: DocumentQuery<Doc> = {} export let query: DocumentQuery<Doc> = {}
export let queryOptions: FindOptions<Doc> | undefined = undefined export let queryOptions: FindOptions<Doc> | undefined = undefined
export let presenter: AnyComponent | undefined = undefined
export let presenterProps: Record<string, any> = {} export let presenterProps: Record<string, any> = {}
export let direction: 'row' | 'column' = 'column' export let direction: 'row' | 'column' = 'column'
export let flipDuration = 200 export let flipDuration = 200
@ -51,11 +51,11 @@
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
const itemsQuery = createQuery() const itemsQuery = createQuery()
let isModelLoading = false let isPresenterLoading = false
let areItemsloading = true let areItemsloading = true
let areItemsSorting = false let areItemsSorting = false
let model: AttributeModel | undefined let presenter: typeof SvelteComponentDev | undefined
let objectFactory: ObjectFactory | undefined let objectFactory: ObjectFactory | undefined
let items: FindResult<Doc> | undefined let items: FindResult<Doc> | undefined
@ -64,12 +64,22 @@
let isCreating = false let isCreating = false
async function updateModel (modelClassRef: Ref<Class<Doc>>, props: Record<string, any>) { async function updatePresenter (classRef: Ref<Class<Doc>>) {
try { try {
isModelLoading = true isPresenterLoading = true
model = await getObjectPresenter(client, modelClassRef, { key: '', props })
const listItemPresenter = await getListItemPresenter(client, classRef)
if (listItemPresenter) {
presenter = await getResource(listItemPresenter)
return
}
const objectModel = await getObjectPresenter(client, classRef, { key: '' })
if (objectModel?.presenter) {
presenter = objectModel.presenter
}
} finally { } finally {
isModelLoading = false isPresenterLoading = false
} }
} }
@ -135,11 +145,11 @@
hoveringIndex = null hoveringIndex = null
} }
$: !presenter && updateModel(_class, presenterProps) $: !$$slots.object && updatePresenter(_class)
$: updateObjectFactory(_class) $: updateObjectFactory(_class)
$: itemsQuery.query(_class, query, updateItems, { ...queryOptions, limit: Math.max(queryOptions?.limit ?? 0, 200) }) $: itemsQuery.query(_class, query, updateItems, { ...queryOptions, limit: Math.max(queryOptions?.limit ?? 0, 200) })
$: isLoading = isModelLoading || areItemsloading $: isLoading = isPresenterLoading || areItemsloading
$: isSortable = hierarchy.getAllAttributes(_class).has('rank') $: isSortable = hierarchy.getAllAttributes(_class).has('rank')
$: itemsCount = items?.length ?? 0 $: itemsCount = items?.length ?? 0
</script> </script>
@ -172,7 +182,7 @@
{#if isLoading} {#if isLoading}
<Loading /> <Loading />
{:else if (presenter || model) && items} {:else if ($$slots.object ?? presenter) && items}
{@const isVertical = direction === 'column'} {@const isVertical = direction === 'column'}
<div class="flex-gap-1" class:flex-col={isVertical} class:flex={!isVertical} class:flex-wrap={!isVertical}> <div class="flex-gap-1" class:flex-col={isVertical} class:flex={!isVertical} class:flex-wrap={!isVertical}>
{#each items as item, index (item._id)} {#each items as item, index (item._id)}
@ -190,10 +200,10 @@
on:drop={() => handleDrop(index)} on:drop={() => handleDrop(index)}
on:dragend={resetDrag} on:dragend={resetDrag}
> >
{#if presenter} {#if $$slots.object}
<Component is={presenter} props={{ isDraggable, ...presenterProps, value: item }} /> <slot name="object" value={item} {isDraggable} />
{:else if model} {:else if presenter}
<svelte:component this={model.presenter} {isDraggable} {...model.props ?? {}} value={item} /> <svelte:component this={presenter} {isDraggable} {...presenterProps} value={item} />
{/if} {/if}
</div> </div>
{/each} {/each}

View File

@ -91,6 +91,20 @@ export async function getObjectPresenter (
} }
} }
/**
* @public
*/
export async function getListItemPresenter (client: Client, _class: Ref<Class<Obj>>): Promise<AnyComponent | undefined> {
const clazz = client.getHierarchy().getClass(_class)
const presenterMixin = client.getHierarchy().as(clazz, view.mixin.ListItemPresenter)
if (presenterMixin.presenter === undefined) {
if (clazz.extends !== undefined) {
return await getListItemPresenter(client, clazz.extends)
}
}
return presenterMixin?.presenter
}
/** /**
* @public * @public
*/ */

View File

@ -128,6 +128,13 @@ export interface AttributePresenter extends Class<Doc> {
presenter: AnyComponent presenter: AnyComponent
} }
/**
* @public
*/
export interface ListItemPresenter extends Class<Doc> {
presenter: AnyComponent
}
/** /**
* @public * @public
*/ */
@ -401,6 +408,7 @@ const view = plugin(viewId, {
InlineAttributEditor: '' as Ref<Mixin<InlineAttributEditor>>, InlineAttributEditor: '' as Ref<Mixin<InlineAttributEditor>>,
ArrayEditor: '' as Ref<Mixin<ArrayEditor>>, ArrayEditor: '' as Ref<Mixin<ArrayEditor>>,
AttributePresenter: '' as Ref<Mixin<AttributePresenter>>, AttributePresenter: '' as Ref<Mixin<AttributePresenter>>,
ListItemPresenter: '' as Ref<Mixin<ListItemPresenter>>,
ObjectEditor: '' as Ref<Mixin<ObjectEditor>>, ObjectEditor: '' as Ref<Mixin<ObjectEditor>>,
ObjectEditorHeader: '' as Ref<Mixin<ObjectEditorHeader>>, ObjectEditorHeader: '' as Ref<Mixin<ObjectEditorHeader>>,
ObjectValidator: '' as Ref<Mixin<ObjectValidator>>, ObjectValidator: '' as Ref<Mixin<ObjectValidator>>,