Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-06-20 21:59:56 +06:00 committed by GitHub
parent 09130c5b69
commit 1091ced274
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 214 additions and 217 deletions

View File

@ -34,6 +34,8 @@
"@anticrm/model-view": "~0.6.0", "@anticrm/model-view": "~0.6.0",
"@anticrm/model-workbench": "~0.6.1", "@anticrm/model-workbench": "~0.6.1",
"@anticrm/model-contact": "~0.6.1", "@anticrm/model-contact": "~0.6.1",
"@anticrm/model-chunter": "~0.6.0",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/hr": "~0.6.0", "@anticrm/hr": "~0.6.0",
"@anticrm/hr-resources": "~0.6.0", "@anticrm/hr-resources": "~0.6.0",
"@anticrm/view": "~0.6.0" "@anticrm/view": "~0.6.0"

View File

@ -14,14 +14,16 @@
// //
import { Employee } from '@anticrm/contact' import { Employee } from '@anticrm/contact'
import contact, { TEmployee } from '@anticrm/model-contact' import contact, { TEmployee, TEmployeeAccount } from '@anticrm/model-contact'
import { IndexKind, Ref } from '@anticrm/core' import { Arr, IndexKind, Ref } from '@anticrm/core'
import type { Department, Staff } from '@anticrm/hr' import type { Department, DepartmentMember, Staff } from '@anticrm/hr'
import { Builder, Index, Mixin, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model' import { Builder, Index, Mixin, Model, Prop, TypeRef, Collection, TypeString, UX, ArrOf } from '@anticrm/model'
import core, { TSpace } from '@anticrm/model-core' import core, { TSpace } from '@anticrm/model-core'
import workbench from '@anticrm/model-workbench' import workbench from '@anticrm/model-workbench'
import hr from './plugin' import hr from './plugin'
import view, { createAction } from '@anticrm/model-view' import view, { createAction } from '@anticrm/model-view'
import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
@Model(hr.class.Department, core.class.Space) @Model(hr.class.Department, core.class.Space)
@UX(hr.string.Department, hr.icon.Department) @UX(hr.string.Department, hr.icon.Department)
@ -33,12 +35,28 @@ export class TDepartment extends TSpace implements Department {
@Index(IndexKind.FullText) @Index(IndexKind.FullText)
name!: string name!: string
@Prop(Collection(contact.class.Channel), contact.string.ContactInfo)
channels?: number
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, undefined, attachment.string.Files)
attachments?: number
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
comments?: number
avatar?: string | null avatar?: string | null
@Prop(TypeRef(contact.class.Employee), hr.string.TeamLead) @Prop(TypeRef(contact.class.Employee), hr.string.TeamLead)
teamLead!: Ref<Employee> | null teamLead!: Ref<Employee> | null
@Prop(ArrOf(TypeRef(hr.class.DepartmentMember)), contact.string.Members)
declare members: Arr<Ref<DepartmentMember>>
} }
@Model(hr.class.DepartmentMember, contact.class.EmployeeAccount)
@UX(contact.string.Employee, hr.icon.HR)
export class TDepartmentMember extends TEmployeeAccount implements DepartmentMember {}
@Mixin(hr.mixin.Staff, contact.class.Employee) @Mixin(hr.mixin.Staff, contact.class.Employee)
@UX(contact.string.Employee, hr.icon.HR) @UX(contact.string.Employee, hr.icon.HR)
export class TStaff extends TEmployee implements Staff { export class TStaff extends TEmployee implements Staff {
@ -47,7 +65,7 @@ export class TStaff extends TEmployee implements Staff {
} }
export function createModel (builder: Builder): void { export function createModel (builder: Builder): void {
builder.createModel(TDepartment, TStaff) builder.createModel(TDepartment, TDepartmentMember, TStaff)
builder.createDoc( builder.createDoc(
workbench.class.Application, workbench.class.Application,
@ -76,6 +94,14 @@ export function createModel (builder: Builder): void {
inlineEditor: hr.component.DepartmentEditor inlineEditor: hr.component.DepartmentEditor
}) })
builder.mixin(hr.class.Department, core.class.Class, view.mixin.ObjectEditor, {
editor: hr.component.EditDepartment
})
builder.mixin(hr.class.DepartmentMember, core.class.Class, view.mixin.ArrayEditor, {
editor: hr.component.DepartmentStaff
})
createAction( createAction(
builder, builder,
{ {

View File

@ -118,7 +118,8 @@ export class TCollectionEditor extends TClass implements CollectionEditor {
@Mixin(view.mixin.ArrayEditor, core.class.Class) @Mixin(view.mixin.ArrayEditor, core.class.Class)
export class TArrayEditor extends TClass implements ArrayEditor { export class TArrayEditor extends TClass implements ArrayEditor {
inlineEditor!: AnyComponent inlineEditor?: AnyComponent
editor?: AnyComponent
} }
@Mixin(view.mixin.AttributePresenter, core.class.Class) @Mixin(view.mixin.AttributePresenter, core.class.Class)

View File

@ -55,6 +55,9 @@
const typeClass = hierarchy.getClass(presenterClass.attrClass) const typeClass = hierarchy.getClass(presenterClass.attrClass)
const editorMixin = hierarchy.as(typeClass, mixinRef) const editorMixin = hierarchy.as(typeClass, mixinRef)
if (category === 'array' && editorMixin.inlineEditor === undefined) {
return
}
editor = getResource(editorMixin.inlineEditor).catch((cause) => { editor = getResource(editorMixin.inlineEditor).catch((cause) => {
console.error(`failed to find editor for ${_class} ${attribute} ${presenterClass.attrClass} cause: ${cause}`) console.error(`failed to find editor for ${_class} ${attribute} ${presenterClass.attrClass} cause: ${cause}`)
}) })

View File

@ -38,6 +38,7 @@
"@anticrm/core": "~0.6.16", "@anticrm/core": "~0.6.16",
"@anticrm/panel": "~0.6.0", "@anticrm/panel": "~0.6.0",
"@anticrm/contact": "~0.6.5", "@anticrm/contact": "~0.6.5",
"@anticrm/view": "~0.6.0",
"@anticrm/view-resources": "~0.6.0", "@anticrm/view-resources": "~0.6.0",
"@anticrm/contact-resources": "~0.6.0", "@anticrm/contact-resources": "~0.6.0",
"@anticrm/setting": "~0.6.1", "@anticrm/setting": "~0.6.1",

View File

@ -19,25 +19,19 @@
import CreateDepartment from './CreateDepartment.svelte' import CreateDepartment from './CreateDepartment.svelte'
import DepartmentCard from './DepartmentCard.svelte' import DepartmentCard from './DepartmentCard.svelte'
import hr from '../plugin' import hr from '../plugin'
import { IconAdd, IconMoreV, Button, eventToHTMLElement, Label, showPopup, ActionIcon } from '@anticrm/ui' import { IconAdd, IconMoreV, Button, eventToHTMLElement, Label, showPopup, ActionIcon, showPanel } from '@anticrm/ui'
import contact, { Employee } from '@anticrm/contact' import contact, { Employee } from '@anticrm/contact'
import { EmployeePresenter } from '@anticrm/contact-resources' import { EmployeePresenter } from '@anticrm/contact-resources'
import DepartmentStaff from './DepartmentStaff.svelte'
import { Menu } from '@anticrm/view-resources' import { Menu } from '@anticrm/view-resources'
import view from '@anticrm/view'
export let value: WithLookup<Department> export let value: WithLookup<Department>
export let descendants: Map<Ref<Department>, WithLookup<Department>[]> export let descendants: Map<Ref<Department>, WithLookup<Department>[]>
$: currentDescendants = descendants.get(value._id) ?? [] $: currentDescendants = descendants.get(value._id) ?? []
let expand = false
const client = getClient() const client = getClient()
function toggle () {
if (currentDescendants.length === 0) return
expand = !expand
}
async function changeLead (result: Employee | null | undefined): Promise<void> { async function changeLead (result: Employee | null | undefined): Promise<void> {
if (result === undefined) { if (result === undefined) {
return return
@ -50,6 +44,8 @@
} }
function openLeadEditor (event: MouseEvent) { function openLeadEditor (event: MouseEvent) {
event?.preventDefault()
event?.stopPropagation()
showPopup( showPopup(
UsersPopup, UsersPopup,
{ {
@ -64,13 +60,7 @@
} }
function createChild (e: MouseEvent) { function createChild (e: MouseEvent) {
showPopup(CreateDepartment, { space: value._id }, eventToHTMLElement(e), (res) => { showPopup(CreateDepartment, { space: value._id }, eventToHTMLElement(e))
if (res && !expand) expand = true
})
}
function editMembers (e: MouseEvent) {
showPopup(DepartmentStaff, { _id: value._id }, 'float')
} }
function showMenu (e: MouseEvent) { function showMenu (e: MouseEvent) {
@ -82,29 +72,30 @@
} }
) )
} }
function edit (e: MouseEvent): void {
showPanel(view.component.EditDoc, value._id, value._class, 'content')
}
</script> </script>
<div class="flex-center w-full px-4"> <div class="flex-center w-full px-4">
<div <div
class="w-full mt-2 mb-2 container flex" class="w-full mt-2 mb-2 container flex"
class:cursor-pointer={currentDescendants.length} class:cursor-pointer={currentDescendants.length}
on:click|stopPropagation={toggle} on:click|stopPropagation={edit}
on:contextmenu|preventDefault={showMenu} on:contextmenu|preventDefault={showMenu}
> >
{#if currentDescendants.length}
<div class="verticalDivider" />
<div class="verticalDivider" />
{/if}
<div class="flex-between pt-4 pb-4 pr-4 pl-2 w-full"> <div class="flex-between pt-4 pb-4 pr-4 pl-2 w-full">
<div class="flex-center"> <div class="flex-center">
<div class="mr-2">
<Button icon={IconAdd} on:click={createChild} />
</div>
<Avatar size={'medium'} avatar={value.avatar} icon={hr.icon.Department} /> <Avatar size={'medium'} avatar={value.avatar} icon={hr.icon.Department} />
<div class="flex-row ml-2"> <div class="flex-row ml-2">
<div class="fs-title"> <div class="fs-title">
{value.name} {value.name}
</div> </div>
<div class="cursor-pointer" on:click|stopPropagation={editMembers}> <Label label={hr.string.MemberCount} params={{ count: value.members.length }} />
<Label label={hr.string.MemberCount} params={{ count: value.members.length }} />
</div>
</div> </div>
</div> </div>
<div class="flex-center mr-2"> <div class="flex-center mr-2">
@ -122,19 +113,16 @@
onEmployeeEdit={openLeadEditor} onEmployeeEdit={openLeadEditor}
/> />
</div> </div>
<Button icon={IconAdd} on:click={createChild} />
<ActionIcon icon={IconMoreV} size={'medium'} action={showMenu} /> <ActionIcon icon={IconMoreV} size={'medium'} action={showMenu} />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{#if expand && currentDescendants.length} <div class="ml-8">
<div class="ml-8"> {#each currentDescendants as nested}
{#each descendants.get(value._id) ?? [] as nested} <DepartmentCard value={nested} {descendants} />
<DepartmentCard value={nested} {descendants} /> {/each}
{/each} </div>
</div>
{/if}
<style lang="scss"> <style lang="scss">
.container { .container {

View File

@ -21,7 +21,7 @@
import hr from '../plugin' import hr from '../plugin'
export let value: Ref<Department> | undefined export let value: Ref<Department> | undefined
export let label: IntlString = hr.string.Department export let label: IntlString = hr.string.ParentDepartmentLabel
export let onChange: (value: any) => void export let onChange: (value: any) => void
export let kind: ButtonKind = 'no-border' export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small' export let size: ButtonSize = 'small'
@ -31,7 +31,7 @@
<SpaceSelector <SpaceSelector
_class={hr.class.Department} _class={hr.class.Department}
label={hr.string.ParentDepartmentLabel} {label}
{size} {size}
{kind} {kind}
{justify} {justify}

View File

@ -13,33 +13,31 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import contact, { Employee, EmployeeAccount } from '@anticrm/contact' import { Employee } from '@anticrm/contact'
import { EmployeePresenter } from '@anticrm/contact-resources'
import contact from '@anticrm/contact-resources/src/plugin'
import { Ref, SortingOrder, WithLookup } from '@anticrm/core' import { Ref, SortingOrder, WithLookup } from '@anticrm/core'
import { Department, Staff } from '@anticrm/hr' import { Department, DepartmentMember, Staff } from '@anticrm/hr'
import { Avatar, createQuery, MessageBox, getClient, UsersPopup } from '@anticrm/presentation' import { createQuery, getClient, MessageBox, UsersPopup } from '@anticrm/presentation'
import { Scroller, Panel, Button, showPopup, eventToHTMLElement } from '@anticrm/ui' import { CircleButton, eventToHTMLElement, IconAdd, Label, Scroller, showPopup } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import StaffPresenter from './StaffPresenter.svelte'
import hr from '../plugin' import hr from '../plugin'
export let _id: Ref<Department> | undefined export let objectId: Ref<Department> | undefined
let value: Department | undefined let value: Department | undefined
let employees: WithLookup<Staff>[] = [] let employees: WithLookup<Staff>[] = []
let accounts: EmployeeAccount[] = [] let accounts: DepartmentMember[] = []
const dispatch = createEventDispatcher()
const departmentQuery = createQuery() const departmentQuery = createQuery()
const query = createQuery() const query = createQuery()
const accountsQuery = createQuery() const accountsQuery = createQuery()
const client = getClient() const client = getClient()
$: _id && $: objectId &&
value === undefined && value === undefined &&
departmentQuery.query( departmentQuery.query(
hr.class.Department, hr.class.Department,
{ {
_id _id: objectId
}, },
(res) => ([value] = res) (res) => ([value] = res)
) )
@ -48,7 +46,7 @@
accountsQuery.query( accountsQuery.query(
contact.class.EmployeeAccount, contact.class.EmployeeAccount,
{ {
_id: { $in: value.members as Ref<EmployeeAccount>[] } _id: { $in: value.members }
}, },
(res) => { (res) => {
accounts = res accounts = res
@ -79,7 +77,7 @@
UsersPopup, UsersPopup,
{ {
_class: contact.class.Employee, _class: contact.class.Employee,
ignoreUsers: employees.filter((p) => p.department === _id).map((p) => p._id) ignoreUsers: employees.filter((p) => p.department === objectId).map((p) => p._id)
}, },
eventToHTMLElement(e), eventToHTMLElement(e),
addMember addMember
@ -87,7 +85,7 @@
} }
async function addMember (employee: Employee | undefined): Promise<void> { async function addMember (employee: Employee | undefined): Promise<void> {
if (employee === undefined || value === undefined) { if (employee === null || employee === undefined || value === undefined) {
return return
} }
@ -131,32 +129,64 @@
} }
</script> </script>
<Panel <div class="container">
isHeader={true} <div class="flex flex-between">
isAside={false} <div class="title"><Label label={contact.string.Members} /></div>
isFullSize <CircleButton id={hr.string.AddEmployee} icon={IconAdd} size={'small'} selected on:click={add} />
on:fullsize </div>
on:close={() => { {#if employees.length > 0}
dispatch('close') <Scroller>
}} <table class="antiTable">
> <thead class="scroller-thead">
<svelte:fragment slot="title"> <tr class="scroller-thead__tr">
<div class="antiTitle icon-wrapper"> <th><Label label={contact.string.Member} /></th>
{#if value} <th><Label label={hr.string.Department} /></th>
<div class="wrapped-icon"><Avatar size={'medium'} avatar={value.avatar} icon={hr.icon.Department} /></div> </tr>
<div class="title-wrapper"> </thead>
<span class="wrapped-title">{value.name}</span> <tbody>
</div> {#each employees as value}
{/if} <tr class="antiTable-body__row">
<td><EmployeePresenter {value} /></td>
<td>
{#if value.$lookup?.department}
{value.$lookup.department.name}
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</Scroller>
{:else}
<div class="flex-col-center mt-5 create-container">
<div class="text-sm content-dark-color mt-2">
<Label label={contact.string.NoMembers} />
</div>
<div class="text-sm">
<div class="over-underline" on:click={add}><Label label={contact.string.AddMember} /></div>
</div>
</div> </div>
</svelte:fragment> {/if}
<svelte:fragment slot="utils"> </div>
<Button label={hr.string.AddEmployee} kind={'primary'} on:click={add} />
</svelte:fragment>
<Scroller> <style lang="scss">
{#each employees as value} .container {
<StaffPresenter {value} /> display: flex;
{/each} flex-direction: column;
</Scroller>
</Panel> .title {
margin-right: 0.75rem;
font-weight: 500;
font-size: 1.25rem;
color: var(--theme-caption-color);
}
}
.create-container {
padding: 1rem;
color: var(--theme-caption-color);
background: var(--theme-bg-accent-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: 0.75rem;
}
</style>

View File

@ -14,11 +14,9 @@
--> -->
<script lang="ts"> <script lang="ts">
import { createQuery, EditableAvatar, getClient } from '@anticrm/presentation' import { createQuery, EditableAvatar, getClient } from '@anticrm/presentation'
import { Panel } from '@anticrm/panel'
import { createFocusManager, EditBox, FocusHandler } from '@anticrm/ui' import { createFocusManager, EditBox, FocusHandler } from '@anticrm/ui'
import { ActionContext } from '@anticrm/view-resources' import { createEventDispatcher, onMount } from 'svelte'
import { createEventDispatcher } from 'svelte'
import { Department } from '@anticrm/hr' import { Department } from '@anticrm/hr'
import core, { getCurrentAccount, Ref, Space } from '@anticrm/core' import core, { getCurrentAccount, Ref, Space } from '@anticrm/core'
import hr from '../plugin' import hr from '../plugin'
@ -27,20 +25,10 @@
import { ChannelsEditor } from '@anticrm/contact-resources' import { ChannelsEditor } from '@anticrm/contact-resources'
import setting, { IntegrationType } from '@anticrm/setting' import setting, { IntegrationType } from '@anticrm/setting'
export let _id: Ref<Department> export let object: Department
let object: Department | undefined
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const client = getClient() const client = getClient()
const query = createQuery()
query.query(
hr.class.Department,
{ _id },
(res) => {
object = res[0]
},
{ limit: 1 }
)
async function onAvatarDone (e: any) { async function onAvatarDone (e: any) {
if (object === undefined) return if (object === undefined) return
@ -77,10 +65,6 @@
const manager = createFocusManager() const manager = createFocusManager()
const _update = (result: any): void => {
dispatch('update', result)
}
let integrations: Set<Ref<IntegrationType>> = new Set<Ref<IntegrationType>>() let integrations: Set<Ref<IntegrationType>> = new Set<Ref<IntegrationType>>()
const accountId = getCurrentAccount()._id const accountId = getCurrentAccount()._id
const settingsQuery = createQuery() const settingsQuery = createQuery()
@ -91,63 +75,46 @@
integrations = new Set(res.map((p) => p.type)) integrations = new Set(res.map((p) => p.type))
} }
) )
</script>
<ActionContext onMount(() => {
context={{ dispatch('open', {
mode: 'editor' ignoreKeys: ['comments', 'name', 'channels', 'private', 'archived'],
}} collectionArrays: ['members']
/> })
})
</script>
<FocusHandler {manager} /> <FocusHandler {manager} />
{#if object !== undefined} {#if object !== undefined}
<Panel <div class="flex-row-stretch flex-grow">
icon={hr.icon.Department} <div class="mr-8">
title={object.name} {#key object}
{object} <EditableAvatar
isHeader={false} avatar={object.avatar}
isAside={true} size={'x-large'}
on:update={(ev) => _update(ev.detail)} icon={hr.icon.Department}
on:close={() => { on:done={onAvatarDone}
dispatch('close') on:remove={removeAvatar}
}} />
> {/key}
<div class="flex-row-stretch flex-grow"> </div>
<div class="mr-8"> <div class="flex-grow flex-col">
{#key object} <div class="name">
<EditableAvatar <EditBox
avatar={object.avatar} placeholder={core.string.Name}
size={'x-large'} maxWidth="20rem"
icon={hr.icon.Department} bind:value={object.name}
on:done={onAvatarDone} on:change={nameChange}
on:remove={removeAvatar} focusIndex={1}
/> />
{/key}
</div> </div>
<div class="flex-grow flex-col"> <div class="separator" />
<div class="name"> <div class="flex-row-center">
<EditBox <ChannelsEditor attachedTo={object._id} attachedClass={object._class} {integrations} focusIndex={10} on:click />
placeholder={core.string.Name}
maxWidth="20rem"
bind:value={object.name}
on:change={nameChange}
focusIndex={1}
/>
</div>
<div class="separator" />
<div class="flex-row-center">
<ChannelsEditor
attachedTo={object._id}
attachedClass={object._class}
{integrations}
focusIndex={10}
on:click
/>
</div>
</div> </div>
</div> </div>
</Panel> </div>
{/if} {/if}
<style lang="scss"> <style lang="scss">

View File

@ -1,36 +0,0 @@
<!--
// 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 { formatName } from '@anticrm/contact'
import { WithLookup } from '@anticrm/core'
import { Staff } from '@anticrm/hr'
import { Avatar } from '@anticrm/presentation'
export let value: WithLookup<Staff>
</script>
<div class="flex-between w-full p-4">
<div class="flex-row-center">
<Avatar avatar={value.avatar} size={'medium'} />
<div class="fs-title ml-2">
{formatName(value.name)}
</div>
</div>
<div>
{#if value.$lookup?.department}
{value.$lookup.department.name}
{/if}
</div>
</div>

View File

@ -13,8 +13,8 @@
// limitations under the License. // limitations under the License.
// //
import type { Employee } from '@anticrm/contact' import type { Employee, EmployeeAccount } from '@anticrm/contact'
import type { Class, Doc, Mixin, Ref, Space } from '@anticrm/core' import type { Arr, Class, Doc, Mixin, Ref, Space } from '@anticrm/core'
import type { Asset, Plugin } from '@anticrm/platform' import type { Asset, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform' import { plugin } from '@anticrm/platform'
@ -25,8 +25,17 @@ export interface Department extends Space {
space: Ref<Department> space: Ref<Department>
avatar?: string | null avatar?: string | null
teamLead: Ref<Employee> | null teamLead: Ref<Employee> | null
attachments?: number
comments?: number
channels?: number
members: Arr<Ref<DepartmentMember>>
} }
/**
* @public
*/
export interface DepartmentMember extends EmployeeAccount {}
/** /**
* @public * @public
*/ */
@ -47,7 +56,8 @@ const hr = plugin(hrId, {
HR: '' as Ref<Doc> HR: '' as Ref<Doc>
}, },
class: { class: {
Department: '' as Ref<Class<Department>> Department: '' as Ref<Class<Department>>,
DepartmentMember: '' as Ref<Class<DepartmentMember>>
}, },
mixin: { mixin: {
Staff: '' as Ref<Mixin<Staff>> Staff: '' as Ref<Mixin<Staff>>

View File

@ -22,23 +22,7 @@
export let allowedCollections: string[] = [] export let allowedCollections: string[] = []
</script> </script>
<ClassAttributeBar <ClassAttributeBar _class={object._class} {object} {ignoreKeys} to={undefined} {allowedCollections} on:update />
_class={object._class}
{object}
{ignoreKeys}
to={undefined}
{allowedCollections}
vertical
on:update
/>
{#each mixins as mixin} {#each mixins as mixin}
<ClassAttributeBar <ClassAttributeBar _class={mixin._id} {object} {ignoreKeys} to={object._class} {allowedCollections} on:update />
_class={mixin._id}
{object}
{ignoreKeys}
to={object._class}
{allowedCollections}
vertical
on:update
/>
{/each} {/each}

View File

@ -91,6 +91,7 @@
let ignoreKeys: string[] = [] let ignoreKeys: string[] = []
let allowedCollections: string[] = [] let allowedCollections: string[] = []
let collectionArrays: string[] = []
let ignoreMixins: Set<Ref<Mixin<Doc>>> = new Set<Ref<Mixin<Doc>>>() let ignoreMixins: Set<Ref<Mixin<Doc>>> = new Set<Ref<Mixin<Doc>>>()
async function updateKeys (): Promise<void> { async function updateKeys (): Promise<void> {
@ -102,12 +103,14 @@
} }
} }
const filtredKeys = Array.from(keysMap.values()) const filtredKeys = Array.from(keysMap.values())
keys = collectionsFilter(hierarchy, filtredKeys, false) keys = collectionsFilter(hierarchy, filtredKeys, false, allowedCollections)
const collectionKeys = collectionsFilter(hierarchy, filtredKeys, true) const collectionKeys = collectionsFilter(hierarchy, filtredKeys, true, collectionArrays)
const editors: { key: KeyedAttribute; editor: AnyComponent }[] = [] const editors: { key: KeyedAttribute; editor: AnyComponent }[] = []
for (const k of collectionKeys) { for (const k of collectionKeys) {
if (allowedCollections.includes(k.key)) continue
const editor = await getCollectionEditor(k) const editor = await getCollectionEditor(k)
if (editor === undefined) continue
editors.push({ key: k, editor }) editors.push({ key: k, editor })
} }
collectionEditors = editors collectionEditors = editors
@ -129,10 +132,11 @@
updateKeys() updateKeys()
} }
async function getCollectionEditor (key: KeyedAttribute): Promise<AnyComponent> { async function getCollectionEditor (key: KeyedAttribute): Promise<AnyComponent | undefined> {
const attrClass = getAttributePresenterClass(hierarchy, key.attr) const attrClass = getAttributePresenterClass(hierarchy, key.attr)
const clazz = hierarchy.getClass(attrClass.attrClass) const clazz = hierarchy.getClass(attrClass.attrClass)
const editorMixin = hierarchy.as(clazz, view.mixin.CollectionEditor) const mixinRef = attrClass.category === 'array' ? view.mixin.ArrayEditor : view.mixin.CollectionEditor
const editorMixin = hierarchy.as(clazz, mixinRef)
return editorMixin.editor return editorMixin.editor
} }
@ -243,7 +247,13 @@
on:update={updateKeys} on:update={updateKeys}
/> />
{:else if dir === 'column'} {:else if dir === 'column'}
<DocAttributeBar {object} {mixins} {ignoreKeys} {allowedCollections} on:update={updateKeys} /> <DocAttributeBar
{object}
{mixins}
ignoreKeys={[...ignoreKeys, ...collectionArrays]}
{allowedCollections}
on:update={updateKeys}
/>
{:else} {:else}
<AttributesBar {object} _class={realObjectClass} {keys} /> <AttributesBar {object} _class={realObjectClass} {keys} />
{/if} {/if}
@ -258,12 +268,13 @@
ignoreKeys = ev.detail.ignoreKeys ignoreKeys = ev.detail.ignoreKeys
ignoreMixins = new Set(ev.detail.ignoreMixins) ignoreMixins = new Set(ev.detail.ignoreMixins)
allowedCollections = ev.detail.allowedCollections ?? [] allowedCollections = ev.detail.allowedCollections ?? []
collectionArrays = ev.detail.collectionArrays ?? []
getMixins(parentClass, object) getMixins(parentClass, object)
updateKeys() updateKeys()
}} }}
/> />
{/if} {/if}
{#each collectionEditors.filter((it) => !allowedCollections.includes(it.key.key)) as collection} {#each collectionEditors as collection}
{#if collection.editor} {#if collection.editor}
<div class="mt-6"> <div class="mt-6">
<Component <Component

View File

@ -381,10 +381,19 @@ export function getFiltredKeys (
return filterKeys(hierarchy, keys, ignoreKeys) return filterKeys(hierarchy, keys, ignoreKeys)
} }
export function collectionsFilter (hierarchy: Hierarchy, keys: KeyedAttribute[], get: boolean): KeyedAttribute[] { export function collectionsFilter (
hierarchy: Hierarchy,
keys: KeyedAttribute[],
get: boolean,
include: string[]
): KeyedAttribute[] {
const result: KeyedAttribute[] = [] const result: KeyedAttribute[] = []
for (const key of keys) { for (const key of keys) {
if (isCollectionAttr(hierarchy, key) === get) result.push(key) if (include.includes(key.key)) {
result.push(key)
} else if (isCollectionAttr(hierarchy, key) === get) {
result.push(key)
}
} }
return result return result
} }

View File

@ -99,7 +99,8 @@ export interface CollectionEditor extends Class<Doc> {
* @public * @public
*/ */
export interface ArrayEditor extends Class<Doc> { export interface ArrayEditor extends Class<Doc> {
inlineEditor: AnyComponent editor?: AnyComponent
inlineEditor?: AnyComponent
} }
/** /**

View File

@ -14,8 +14,8 @@
// //
import contact, { Employee } from '@anticrm/contact' import contact, { Employee } from '@anticrm/contact'
import core, { Account, Ref, SortingOrder, Tx, TxFactory, TxMixin } from '@anticrm/core' import core, { Ref, SortingOrder, Tx, TxFactory, TxMixin } from '@anticrm/core'
import hr, { Department, Staff } from '@anticrm/hr' import hr, { Department, DepartmentMember, Staff } from '@anticrm/hr'
import { extractTx, TriggerControl } from '@anticrm/server-core' import { extractTx, TriggerControl } from '@anticrm/server-core'
async function getOldDepartment ( async function getOldDepartment (
@ -67,7 +67,7 @@ function exlude (first: Ref<Department>[], second: Ref<Department>[]): Ref<Depar
function getTxes ( function getTxes (
factory: TxFactory, factory: TxFactory,
account: Ref<Account>, account: Ref<DepartmentMember>,
added: Ref<Department>[], added: Ref<Department>[],
removed?: Ref<Department>[] removed?: Ref<Department>[]
): Tx[] { ): Tx[] {