mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-09 09:20:54 +00:00
Custom ref attributes (#1695)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
9951132b3c
commit
de9de716b5
@ -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
|
||||
})
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
})
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
30
plugins/contact-resources/src/components/PersonEditor.svelte
Normal file
30
plugins/contact-resources/src/components/PersonEditor.svelte
Normal 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} />
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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>
|
@ -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,
|
||||
|
@ -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>>,
|
||||
|
Loading…
Reference in New Issue
Block a user