UBER-1187: AnyType field support (#4343)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-01-12 11:33:18 +07:00 committed by GitHub
parent f972066df6
commit 262c2dd82c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 100 additions and 39 deletions

View File

@ -14,6 +14,13 @@
// //
import { import {
DOMAIN_BLOB,
DOMAIN_CONFIGURATION,
DOMAIN_DOC_INDEX_STATE,
DOMAIN_FULLTEXT_BLOB,
DOMAIN_MIGRATION,
DOMAIN_MODEL,
IndexKind,
type Account, type Account,
type AnyAttribute, type AnyAttribute,
type ArrOf, type ArrOf,
@ -27,21 +34,15 @@ import {
type Doc, type Doc,
type DocIndexState, type DocIndexState,
type Domain, type Domain,
DOMAIN_BLOB,
DOMAIN_CONFIGURATION,
DOMAIN_MIGRATION,
DOMAIN_DOC_INDEX_STATE,
DOMAIN_FULLTEXT_BLOB,
DOMAIN_MODEL,
type Enum, type Enum,
type EnumOf, type EnumOf,
type FieldIndex, type FieldIndex,
type FullTextData, type FullTextData,
type FullTextSearchContext, type FullTextSearchContext,
type IndexingConfiguration,
IndexKind,
type IndexStageState, type IndexStageState,
type IndexingConfiguration,
type Interface, type Interface,
type MigrationState,
type Mixin, type Mixin,
type Obj, type Obj,
type PluginConfiguration, type PluginConfiguration,
@ -50,8 +51,8 @@ import {
type Space, type Space,
type Timestamp, type Timestamp,
type Type, type Type,
type Version, type TypeAny,
type MigrationState type Version
} from '@hcengineering/core' } from '@hcengineering/core'
import { import {
Hidden, Hidden,
@ -243,6 +244,13 @@ export class TEnumOf extends TType implements EnumOf {
of!: Ref<Enum> of!: Ref<Enum>
} }
@UX(getEmbeddedLabel('Any'))
@Model(core.class.TypeAny, core.class.Type)
export class TTypeAny extends TType implements TypeAny {
presenter!: any
editor!: any
}
@Model(core.class.Version, core.class.Doc, DOMAIN_MODEL) @Model(core.class.Version, core.class.Doc, DOMAIN_MODEL)
export class TVersion extends TDoc implements Version { export class TVersion extends TDoc implements Version {
major!: number major!: number

View File

@ -15,13 +15,13 @@
import { import {
AccountRole, AccountRole,
systemAccountEmail,
type AttachedDoc, type AttachedDoc,
type Class, type Class,
type Doc, type Doc,
type DocIndexState, type DocIndexState,
type IndexingConfiguration, type IndexingConfiguration,
type TxCollectionCUD, type TxCollectionCUD
systemAccountEmail
} from '@hcengineering/core' } from '@hcengineering/core'
import { type Builder } from '@hcengineering/model' import { type Builder } from '@hcengineering/model'
import core from './component' import core from './component'
@ -49,6 +49,7 @@ import {
TPluginConfiguration, TPluginConfiguration,
TRefTo, TRefTo,
TType, TType,
TTypeAny,
TTypeAttachment, TTypeAttachment,
TTypeBoolean, TTypeBoolean,
TTypeCollaborativeMarkup, TTypeCollaborativeMarkup,
@ -126,6 +127,7 @@ export function createModel (builder: Builder): void {
TPluginConfiguration, TPluginConfiguration,
TUserStatus, TUserStatus,
TEnum, TEnum,
TTypeAny,
TBlobData, TBlobData,
TFulltextData, TFulltextData,
TTypeRelatedDocument, TTypeRelatedDocument,

View File

@ -291,6 +291,16 @@ export interface EnumOf extends Type<string> {
*/ */
export interface TypeHyperlink extends Type<Hyperlink> {} export interface TypeHyperlink extends Type<Hyperlink> {}
/**
* @public
*
* A type for some custom serialized field with a set of editors
*/
export interface TypeAny<AnyComponent = any> extends Type<any> {
presenter: AnyComponent
editor?: AnyComponent
}
/** /**
* @public * @public
*/ */

View File

@ -45,6 +45,7 @@ import type {
Space, Space,
Timestamp, Timestamp,
Type, Type,
TypeAny,
UserStatus UserStatus
} from './classes' } from './classes'
import { Status, StatusCategory } from './status' import { Status, StatusCategory } from './status'
@ -109,6 +110,7 @@ export default plugin(coreId, {
Enum: '' as Ref<Class<Enum>>, Enum: '' as Ref<Class<Enum>>,
EnumOf: '' as Ref<Class<EnumOf>>, EnumOf: '' as Ref<Class<EnumOf>>,
Collection: '' as Ref<Class<Collection<AttachedDoc>>>, Collection: '' as Ref<Class<Collection<AttachedDoc>>>,
TypeAny: '' as Ref<Class<TypeAny>>,
Version: '' as Ref<Class<Version>>, Version: '' as Ref<Class<Version>>,
PluginConfiguration: '' as Ref<Class<PluginConfiguration>>, PluginConfiguration: '' as Ref<Class<PluginConfiguration>>,
UserStatus: '' as Ref<Class<UserStatus>>, UserStatus: '' as Ref<Class<UserStatus>>,

View File

@ -15,25 +15,22 @@
import core, { import core, {
Account, Account,
ArrOf as TypeArrOf,
AttachedDoc, AttachedDoc,
Attribute, Attribute,
Class, Class,
Classifier, Classifier,
ClassifierKind, ClassifierKind,
Collection as TypeCollection,
Data, Data,
DateRangeMode, DateRangeMode,
Doc, Doc,
Domain, Domain,
Enum, Enum,
EnumOf, EnumOf,
generateId,
Hyperlink, Hyperlink,
Mixin as IMixin,
IndexKind, IndexKind,
Interface, Interface,
Markup, Markup,
Mixin as IMixin,
MixinUpdate, MixinUpdate,
Obj, Obj,
PropertyType, PropertyType,
@ -46,7 +43,11 @@ import core, {
TxFactory, TxFactory,
TxProcessor, TxProcessor,
Type, Type,
TypeDate as TypeDateType TypeAny as TypeAnyType,
ArrOf as TypeArrOf,
Collection as TypeCollection,
TypeDate as TypeDateType,
generateId
} from '@hcengineering/core' } from '@hcengineering/core'
import type { Asset, IntlString } from '@hcengineering/platform' import type { Asset, IntlString } from '@hcengineering/platform'
import toposort from 'toposort' import toposort from 'toposort'
@ -464,6 +465,17 @@ export function TypeEnum (of: Ref<Enum>): EnumOf {
return { _class: core.class.EnumOf, label: core.string.Enum, of } return { _class: core.class.EnumOf, label: core.string.Enum, of }
} }
/**
* @public
*/
export function TypeAny<AnyComponent = any> (
presenter: AnyComponent,
label: IntlString,
editor?: AnyComponent
): TypeAnyType<AnyComponent> {
return { _class: core.class.TypeAny, label, presenter, editor }
}
/** /**
* @public * @public
*/ */

View File

@ -40,7 +40,7 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let editor: AnySvelteComponent | undefined let editor: AnySvelteComponent | undefined
function onChange (value: any) { function onChange (value: any): void {
const doc = object as Doc const doc = object as Doc
dispatch('update', { key, value }) dispatch('update', { key, value })
@ -48,12 +48,14 @@
if (draft) { if (draft) {
;(doc as any)[attributeKey] = value ;(doc as any)[attributeKey] = value
} else { } else {
updateAttribute(client, doc, doc._class, { key: attributeKey, attr: attribute }, value) void updateAttribute(client, doc, doc._class, { key: attributeKey, attr: attribute }, value)
} }
} }
function getEditor (_class: Ref<Class<Doc>>, key: KeyedAttribute | string) { function getEditor (_class: Ref<Class<Doc>>, key: KeyedAttribute | string): void {
getAttributeEditor(client, _class, key).then((p) => (editor = p)) void getAttributeEditor(client, _class, key).then((p) => {
editor = p
})
} }
$: getEditor(_class, key) $: getEditor(_class, key)

View File

@ -48,10 +48,10 @@
} }
} }
function onChange (value: any) { function onChange (value: any): void {
if (!editable) return if (!editable) return
const doc = object as Doc const doc = object as Doc
updateAttribute(client, doc, _class, { key: attributeKey, attr: attribute }, value) void updateAttribute(client, doc, _class, { key: attributeKey, attr: attribute }, value)
} }
</script> </script>

View File

@ -15,6 +15,9 @@
// //
import core, { import core, {
TxOperations,
type TypeAny,
getCurrentAccount,
type AnyAttribute, type AnyAttribute,
type ArrOf, type ArrOf,
type AttachedDoc, type AttachedDoc,
@ -25,28 +28,26 @@ import core, {
type DocumentQuery, type DocumentQuery,
type FindOptions, type FindOptions,
type FindResult, type FindResult,
getCurrentAccount,
type Hierarchy, type Hierarchy,
type Mixin, type Mixin,
type Obj, type Obj,
type Ref, type Ref,
type RefTo, type RefTo,
type Tx,
TxOperations,
type TxResult,
type WithLookup,
type SearchQuery,
type SearchOptions, type SearchOptions,
type SearchResult type SearchQuery,
type SearchResult,
type Tx,
type TxResult,
type WithLookup
} from '@hcengineering/core' } from '@hcengineering/core'
import { getMetadata, getResource } from '@hcengineering/platform' import { getMetadata, getResource } from '@hcengineering/platform'
import { LiveQuery as LQ } from '@hcengineering/query' import { LiveQuery as LQ } from '@hcengineering/query'
import { type AnySvelteComponent, type IconSize } from '@hcengineering/ui' import { type AnyComponent, type AnySvelteComponent, type IconSize } from '@hcengineering/ui'
import view, { type AttributeEditor } from '@hcengineering/view' import view, { type AttributeEditor } from '@hcengineering/view'
import { deepEqual } from 'fast-equals' import { deepEqual } from 'fast-equals'
import { onDestroy } from 'svelte' import { onDestroy } from 'svelte'
import { type KeyedAttribute } from '..' import { type KeyedAttribute } from '..'
import { OptimizeQueryMiddleware, type PresentationPipeline, PresentationPipelineImpl } from './pipeline' import { OptimizeQueryMiddleware, PresentationPipelineImpl, type PresentationPipeline } from './pipeline'
import plugin from './plugin' import plugin from './plugin'
let liveQuery: LQ let liveQuery: LQ
@ -406,6 +407,12 @@ export async function getAttributeEditor (
): Promise<AnySvelteComponent | undefined> { ): Promise<AnySvelteComponent | undefined> {
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
const attribute = typeof key === 'string' ? hierarchy.getAttribute(_class, key) : key.attr const attribute = typeof key === 'string' ? hierarchy.getAttribute(_class, key) : key.attr
if (attribute.type._class === core.class.TypeAny) {
const _type: TypeAny = attribute.type as TypeAny<AnyComponent>
return await getResource(_type.editor ?? _type.presenter)
}
const presenterClass = attribute !== undefined ? getAttributePresenterClass(hierarchy, attribute) : undefined const presenterClass = attribute !== undefined ? getAttributePresenterClass(hierarchy, attribute) : undefined
if (presenterClass === undefined) { if (presenterClass === undefined) {

View File

@ -19,13 +19,14 @@
import { Panel } from '@hcengineering/panel' import { Panel } from '@hcengineering/panel'
import { getResource } from '@hcengineering/platform' import { getResource } from '@hcengineering/platform'
import presentation, { import presentation, {
createQuery,
getClient,
ActionContext, ActionContext,
ComponentExtensions,
contextStore, contextStore,
ComponentExtensions createQuery,
getClient
} from '@hcengineering/presentation' } from '@hcengineering/presentation'
import setting, { settingId } from '@hcengineering/setting' import setting, { settingId } from '@hcengineering/setting'
import { taskTypeStore, typeStore } from '@hcengineering/task-resources'
import { Issue, Project } from '@hcengineering/tracker' import { Issue, Project } from '@hcengineering/tracker'
import { import {
AnyComponent, AnyComponent,
@ -42,8 +43,8 @@
navigate, navigate,
showPopup showPopup
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { ContextMenu, DocNavLink, ParentsNavigator } from '@hcengineering/view-resources'
import view from '@hcengineering/view' import view from '@hcengineering/view'
import { ContextMenu, DocNavLink, ParentsNavigator } from '@hcengineering/view-resources'
import { createEventDispatcher, onDestroy } from 'svelte' import { createEventDispatcher, onDestroy } from 'svelte'
import { generateIssueShortLink, getIssueId } from '../../../issues' import { generateIssueShortLink, getIssueId } from '../../../issues'
import tracker from '../../../plugin' import tracker from '../../../plugin'
@ -161,6 +162,10 @@
$: editorFooter = getEditorFooter(issue?._class) $: editorFooter = getEditorFooter(issue?._class)
let content: HTMLElement let content: HTMLElement
$: taskType = issue?.kind !== undefined ? $taskTypeStore.get(issue?.kind) : undefined
$: projectType = taskType?.parent !== undefined ? $typeStore.get(taskType.parent) : undefined
</script> </script>
{#if !embedded} {#if !embedded}
@ -187,12 +192,24 @@
on:select on:select
> >
<svelte:fragment slot="title"> <svelte:fragment slot="title">
{#if !embedded}<ParentsNavigator element={issue} />{/if} {#if !embedded}
<ParentsNavigator element={issue} />
{/if}
{#if embedded && issueId} {#if embedded && issueId}
<DocNavLink noUnderline object={issue}> <DocNavLink noUnderline object={issue}>
<div class="title">{issueId}</div> <div class="title">{issueId}</div>
</DocNavLink> </DocNavLink>
{:else if issueId}<div class="title not-active">{issueId}</div>{/if} {:else if issueId}
<div class="title not-active">{issueId}</div>
{/if}
{#if (projectType?.tasks.length ?? 0) > 1 && taskType !== undefined}
({taskType.name})
{/if}
<ComponentExtensions
extension={tracker.extensions.EditIssueTitle}
props={{ size: 'medium', value: issue, space: currentProject }}
/>
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="pre-utils"> <svelte:fragment slot="pre-utils">
<ComponentExtensions <ComponentExtensions

View File

@ -532,7 +532,8 @@ const pluginState = plugin(trackerId, {
}, },
extensions: { extensions: {
IssueListHeader: '' as ComponentExtensionId, IssueListHeader: '' as ComponentExtensionId,
EditIssueHeader: '' as ComponentExtensionId EditIssueHeader: '' as ComponentExtensionId,
EditIssueTitle: '' as ComponentExtensionId
}, },
taskTypes: { taskTypes: {
Issue: '' as Ref<TaskType>, Issue: '' as Ref<TaskType>,