Custom ref attributes (#1695)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-05-06 23:48:43 +06:00 committed by GitHub
parent 9951132b3c
commit de9de716b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 176 additions and 72 deletions

View File

@ -64,11 +64,11 @@ export function createModel (builder: Builder): void {
presenter: attachment.component.AttachmentPresenter
})
builder.mixin(attachment.class.Attachment, core.class.Class, view.mixin.AttributeEditor, {
builder.mixin(attachment.class.Attachment, core.class.Class, view.mixin.CollectionEditor, {
editor: attachment.component.Attachments
})
builder.mixin(attachment.class.Photo, core.class.Class, view.mixin.AttributeEditor, {
builder.mixin(attachment.class.Photo, core.class.Class, view.mixin.CollectionEditor, {
editor: attachment.component.Photos
})

View File

@ -181,6 +181,14 @@ export function createModel (builder: Builder): void {
editor: contact.component.OrganizationEditor
})
builder.mixin(contact.class.Person, core.class.Class, view.mixin.AttributeEditor, {
editor: contact.component.PersonEditor
})
builder.mixin(contact.class.Employee, core.class.Class, view.mixin.AttributeEditor, {
editor: contact.component.PersonEditor
})
builder.mixin(contact.class.Channel, core.class.Class, view.mixin.AttributePresenter, {
presenter: contact.component.ChannelsPresenter
})

View File

@ -35,6 +35,7 @@ export default mergeIds(contactId, contact, {
OrganizationPresenter: '' as AnyComponent,
Contacts: '' as AnyComponent,
EmployeeAccountPresenter: '' as AnyComponent,
PersonEditor: '' as AnyComponent,
OrganizationEditor: '' as AnyComponent
},
string: {
@ -47,7 +48,6 @@ export default mergeIds(contactId, contact, {
Location: '' as IntlString,
Channel: '' as IntlString,
ChannelProvider: '' as IntlString,
Person: '' as IntlString,
Employee: '' as IntlString,
Value: '' as IntlString,
Phone: '' as IntlString,

View File

@ -86,7 +86,7 @@ export function createModel (builder: Builder): void {
presenter: inventory.component.VariantPresenter
})
builder.mixin(inventory.class.Variant, core.class.Class, view.mixin.AttributeEditor, {
builder.mixin(inventory.class.Variant, core.class.Class, view.mixin.CollectionEditor, {
editor: inventory.component.Variants
})

View File

@ -185,7 +185,7 @@ export function createModel (builder: Builder): void {
presenter: lead.component.LeadPresenter
})
builder.mixin(lead.class.Lead, core.class.Class, view.mixin.AttributeEditor, {
builder.mixin(lead.class.Lead, core.class.Class, view.mixin.CollectionEditor, {
editor: lead.component.Leads
})

View File

@ -133,7 +133,7 @@ export function createModel (builder: Builder): void {
}
})
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.AttributeEditor, {
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.CollectionEditor, {
editor: recruit.component.Applications
})

View File

@ -43,7 +43,7 @@ export function createReviewModel (builder: Builder): void {
}
})
builder.mixin(recruit.class.Review, core.class.Class, view.mixin.AttributeEditor, {
builder.mixin(recruit.class.Review, core.class.Class, view.mixin.CollectionEditor, {
editor: recruit.component.Reviews
})
@ -135,7 +135,7 @@ function createTableViewlet (builder: Builder): void {
config: reviewTableConfig
})
builder.mixin(recruit.class.Opinion, core.class.Class, view.mixin.AttributeEditor, {
builder.mixin(recruit.class.Opinion, core.class.Class, view.mixin.CollectionEditor, {
editor: recruit.component.Opinions
})
}

View File

@ -84,7 +84,7 @@ export class TTagCategory extends TDoc implements TagCategory {
export function createModel (builder: Builder): void {
builder.createModel(TTagElement, TTagReference, TTagCategory)
builder.mixin(tags.class.TagReference, core.class.Class, view.mixin.AttributeEditor, {
builder.mixin(tags.class.TagReference, core.class.Class, view.mixin.CollectionEditor, {
editor: tags.component.Tags
})

View File

@ -39,7 +39,8 @@ import presentation from '@anticrm/model-presentation'
import view, { actionTemplates as viewTemplates, createAction, template } from '@anticrm/model-view'
import { IntlString } from '@anticrm/platform'
import { ViewAction } from '@anticrm/view'
import type {
import {
DOMAIN_STATE,
DoneState,
DoneStateTemplate,
Issue,
@ -65,7 +66,6 @@ export { createKanbanTemplate, createSequence, taskOperation } from './migration
export { default } from './plugin'
export const DOMAIN_TASK = 'task' as Domain
export const DOMAIN_STATE = 'state' as Domain
export const DOMAIN_KANBAN = 'kanban' as Domain
@Model(task.class.State, core.class.Doc, DOMAIN_STATE, [task.interface.DocWithRank])
@UX(task.string.TaskState, task.icon.TaskState, undefined, 'rank')
@ -454,7 +454,7 @@ export function createModel (builder: Builder): void {
task.viewlet.Kanban
)
builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.AttributeEditor, {
builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.CollectionEditor, {
editor: task.component.Todos
})

View File

@ -24,6 +24,7 @@ import type {
ActionCategory,
AttributeEditor,
AttributePresenter,
CollectionEditor,
HTMLPresenter,
IgnoreActions,
KeyBinding,
@ -77,6 +78,11 @@ export class TAttributeEditor extends TClass implements AttributeEditor {
editor!: AnyComponent
}
@Mixin(view.mixin.CollectionEditor, core.class.Class)
export class TCollectionEditor extends TClass implements CollectionEditor {
editor!: AnyComponent
}
@Mixin(view.mixin.AttributePresenter, core.class.Class)
export class TAttributePresenter extends TClass implements AttributePresenter {
presenter!: AnyComponent
@ -210,6 +216,7 @@ export function createModel (builder: Builder): void {
builder.createModel(
TAttributeEditor,
TAttributePresenter,
TCollectionEditor,
TObjectEditor,
TViewletDescriptor,
TViewlet,
@ -252,6 +259,10 @@ export function createModel (builder: Builder): void {
editor: view.component.NumberTypeEditor
})
builder.mixin(core.class.RefTo, core.class.Class, view.mixin.ObjectEditor, {
editor: view.component.RefEditor
})
builder.createDoc(
view.class.ActionCategory,
core.space.Model,

View File

@ -77,7 +77,8 @@ export default mergeIds(viewId, view, {
StringTypeEditor: '' as AnyComponent,
BooleanTypeEditor: '' as AnyComponent,
NumberTypeEditor: '' as AnyComponent,
DateTypeEditor: '' as AnyComponent
DateTypeEditor: '' as AnyComponent,
RefEditor: '' as AnyComponent
},
string: {
Table: '' as IntlString,

View File

@ -0,0 +1,30 @@
<!--
// Copyright © 2022 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 { Person } from '@anticrm/contact'
import { Ref, RefTo } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { UserBox } from '@anticrm/presentation'
import contact from '../plugin'
export let value: Ref<Person> | undefined
export let label: IntlString = contact.string.Person
export let onChange: (value: any) => void
export let type: RefTo<Person> | undefined
$: _class = type?.to ?? contact.class.Person
</script>
<UserBox {_class} {label} kind={'link'} size={'large'} bind:value on:change={onChange} />

View File

@ -37,6 +37,7 @@ import SocialEditor from './components/SocialEditor.svelte'
import contact from './plugin'
import EmployeeAccountPresenter from './components/EmployeeAccountPresenter.svelte'
import OrganizationEditor from './components/OrganizationEditor.svelte'
import PersonEditor from './components/PersonEditor.svelte'
import OrganizationSelector from './components/OrganizationSelector.svelte'
export { Channels, ChannelsEditor, ContactPresenter, ChannelsView, OrganizationSelector, ChannelsDropdown }
@ -58,6 +59,7 @@ async function queryContact (
export default async (): Promise<Resources> => ({
component: {
PersonEditor,
OrganizationEditor,
ContactPresenter,
PersonPresenter,

View File

@ -33,6 +33,7 @@ export default mergeIds(contactId, contact, {
CreateOrganizations: '' as IntlString,
Organizations: '' as IntlString,
Organization: '' as IntlString,
Person: '' as IntlString,
SelectFolder: '' as IntlString,
OrganizationsFolder: '' as IntlString,
PersonsFolder: '' as IntlString,

View File

@ -17,7 +17,6 @@
import contact from '@anticrm/contact'
import core, { Class, Doc, Mixin, Ref, RefTo } from '@anticrm/core'
import { AttributesBar, getClient, KeyedAttribute, UserBox } from '@anticrm/presentation'
import { Label } from '@anticrm/ui'
import { Task } from '@anticrm/task'
import task from '../plugin'
import { DocAttributeBar } from '@anticrm/view-resources'
@ -78,38 +77,10 @@
<AttributesBar {object} keys={['doneState', 'state']} showHeader={false} />
</div>
{:else}
<div class="task-attr-prop mb-4">
<span class="fs-bold"><Label label={task.string.TaskAssignee} /></span>
<UserBox
_class={getAssigneeClass(object)}
label={assigneeTitle}
placeholder={assigneeTitle}
kind={'link'}
size={'large'}
bind:value={object.assignee}
on:change={change}
allowDeselect
titleDeselect={task.string.TaskUnAssign}
/>
<div style:grid-column={'1/3'}>
<AttributesBar {object} keys={['doneState', 'state']} vertical />
</div>
</div>
<DocAttributeBar {object} ignoreKeys={[...ignoreKeys, ...taskKeys]} {mixins} on:update />
<DocAttributeBar {object} {ignoreKeys} {mixins} on:update />
{/if}
<style lang="scss">
.task-attr-prop {
display: grid;
grid-template-columns: 1fr 1.5fr;
grid-template-rows: minmax(2rem, auto);
grid-auto-flow: row;
justify-content: start;
align-items: center;
gap: 1rem;
width: 100%;
height: min-content;
}
.task-attr-header {
display: flex;
justify-content: space-between;

View File

@ -14,7 +14,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Ref } from '@anticrm/core'
import { Ref, Space } from '@anticrm/core'
import task, { State } from '@anticrm/task'
import { createQuery } from '@anticrm/presentation'
import type { ButtonKind, ButtonSize } from '@anticrm/ui'
@ -23,6 +23,7 @@
import StatesPopup from './StatesPopup.svelte'
export let value: Ref<State>
export let space: Ref<Space>
export let onChange: (value: any) => void
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
@ -41,26 +42,26 @@
)
</script>
{#if state}
<Button
width="min-content"
{kind}
{size}
on:click={(ev) => {
if (!opened) {
opened = true
showPopup(StatesPopup, { space: state.space }, eventToHTMLElement(ev), (result) => {
if (result && result._id !== value) {
value = result._id
onChange(value)
}
opened = false
})
}
}}
>
<svelte:fragment slot="content">
<Button
width="min-content"
{kind}
{size}
on:click={(ev) => {
if (!opened) {
opened = true
showPopup(StatesPopup, { space }, eventToHTMLElement(ev), (result) => {
if (result && result._id !== value) {
value = result._id
onChange(value)
}
opened = false
})
}
}}
>
<svelte:fragment slot="content">
{#if state}
<StatePresenter value={state} />
</svelte:fragment>
</Button>
{/if}
{/if}
</svelte:fragment>
</Button>

View File

@ -23,7 +23,7 @@
{#if value}
<div class="flex-row-center">
<div class="state-container" style="background-color: {getPlatformColor(value.color)}" />
<span class="overflow-label">{value.title ?? ''}</span>
<span class="overflow-label">{value.title}</span>
</div>
{/if}

View File

@ -14,13 +14,30 @@
//
import type { Employee } from '@anticrm/contact'
import { AttachedDoc, Class, Doc, Interface, Markup, Mixin, Ref, Space, Timestamp, TxOperations } from '@anticrm/core'
import {
AttachedDoc,
Class,
Doc,
Domain,
Interface,
Markup,
Mixin,
Ref,
Space,
Timestamp,
TxOperations
} from '@anticrm/core'
import type { Asset, IntlString, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui'
import { ViewletDescriptor } from '@anticrm/view'
import { genRanks } from './utils'
/**
* @public
*/
export const DOMAIN_STATE = 'state' as Domain
/**
* @public
*/

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import core, { AnyAttribute, Class, Data, generateId, IndexKind, PropertyType, Ref, Space, Type } from '@anticrm/core'
import core, { AnyAttribute, Class, Data, Doc, generateId, IndexKind, PropertyType, Ref, Type } from '@anticrm/core'
import { getEmbeddedLabel } from '@anticrm/platform'
import { Card, getClient } from '@anticrm/presentation'
import { AnyComponent, Component, DropdownLabelsIntl, EditBox, Label } from '@anticrm/ui'
@ -21,7 +21,7 @@
import { createEventDispatcher } from 'svelte'
import view from '../plugin'
export let _class: Ref<Class<Space>>
export let _class: Ref<Class<Doc>>
let name: string
let type: Type<PropertyType> | undefined
let index: IndexKind | undefined

View File

@ -135,8 +135,8 @@
async function getCollectionEditor (key: KeyedAttribute): Promise<AnyComponent> {
const attrClass = getAttributePresenterClass(key.attr)
const clazz = client.getHierarchy().getClass(attrClass)
const editorMixin = client.getHierarchy().as(clazz, view.mixin.AttributeEditor)
const clazz = hierarchy.getClass(attrClass)
const editorMixin = hierarchy.as(clazz, view.mixin.CollectionEditor)
return editorMixin.editor
}

View File

@ -0,0 +1,52 @@
<!--
// Copyright © 2022 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 core, { Class, Doc, Ref } from '@anticrm/core'
import { TypeRef } from '@anticrm/model'
import { getClient } from '@anticrm/presentation'
import { DOMAIN_STATE } from '@anticrm/task'
import { DropdownLabelsIntl, Label } from '@anticrm/ui'
import view from '../../plugin'
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
const client = getClient()
const hierarchy = client.getHierarchy()
const descendants = hierarchy.getDescendants(core.class.Doc)
const classes = descendants
.map((p) => hierarchy.getClass(p))
.filter((p) => {
return (
hierarchy.hasMixin(p, view.mixin.AttributeEditor) &&
p.label !== undefined &&
hierarchy.getDomain(p._id) !== DOMAIN_STATE
)
})
.map((p) => {
return { id: p._id, label: p.label }
})
let refClass: Ref<Class<Doc>>
$: dispatch('change', { type: TypeRef(refClass) })
</script>
<div class="flex-row-center flex-grow">
<Label label={core.string.Class} />
<div class="ml-4">
<DropdownLabelsIntl label={core.string.Class} items={classes} width="8rem" bind:selected={refClass} />
</div>
</div>

View File

@ -43,6 +43,7 @@ import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte'
import BooleanTypeEditor from './components/typeEditors/BooleanTypeEditor.svelte'
import DateTypeEditor from './components/typeEditors/DateTypeEditor.svelte'
import NumberTypeEditor from './components/typeEditors/NumberTypeEditor.svelte'
import RefEditor from './components/typeEditors/RefEditor.svelte'
import DocAttributeBar from './components/DocAttributeBar.svelte'
export { getActions } from './actions'
@ -63,6 +64,7 @@ export default async (): Promise<Resources> => ({
StringTypeEditor,
BooleanTypeEditor,
NumberTypeEditor,
RefEditor,
DateTypeEditor,
SpacePresenter,
StringEditor,

View File

@ -39,6 +39,13 @@ export interface AttributeEditor extends Class<Doc> {
editor: AnyComponent
}
/**
* @public
*/
export interface CollectionEditor extends Class<Doc> {
editor: AnyComponent
}
/**
* @public
*/
@ -286,6 +293,7 @@ export interface ObjectFactory extends Class<Obj> {
const view = plugin(viewId, {
mixin: {
AttributeEditor: '' as Ref<Mixin<AttributeEditor>>,
CollectionEditor: '' as Ref<Mixin<CollectionEditor>>,
AttributePresenter: '' as Ref<Mixin<AttributePresenter>>,
ObjectEditor: '' as Ref<Mixin<ObjectEditor>>,
ObjectEditorHeader: '' as Ref<Mixin<ObjectEditorHeader>>,