mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-23 08:48:01 +00:00
UBERF-5827: add collaborative description for companies (#4851)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
parent
e003fb29fb
commit
1a1b978f82
@ -40,7 +40,8 @@ import {
|
|||||||
type Class,
|
type Class,
|
||||||
type Domain,
|
type Domain,
|
||||||
type Ref,
|
type Ref,
|
||||||
type Timestamp
|
type Timestamp,
|
||||||
|
type Markup
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import {
|
import {
|
||||||
Collection,
|
Collection,
|
||||||
@ -57,7 +58,8 @@ import {
|
|||||||
TypeString,
|
TypeString,
|
||||||
TypeTimestamp,
|
TypeTimestamp,
|
||||||
UX,
|
UX,
|
||||||
type Builder
|
type Builder,
|
||||||
|
TypeCollaborativeMarkup
|
||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
import attachment from '@hcengineering/model-attachment'
|
import attachment from '@hcengineering/model-attachment'
|
||||||
import chunter from '@hcengineering/model-chunter'
|
import chunter from '@hcengineering/model-chunter'
|
||||||
@ -156,6 +158,10 @@ export class TMember extends TAttachedDoc implements Member {
|
|||||||
@Model(contact.class.Organization, contact.class.Contact)
|
@Model(contact.class.Organization, contact.class.Contact)
|
||||||
@UX(contact.string.Organization, contact.icon.Company, 'ORG', 'name')
|
@UX(contact.string.Organization, contact.icon.Company, 'ORG', 'name')
|
||||||
export class TOrganization extends TContact implements Organization {
|
export class TOrganization extends TContact implements Organization {
|
||||||
|
@Prop(TypeCollaborativeMarkup(), core.string.Description)
|
||||||
|
@Index(IndexKind.FullText)
|
||||||
|
description?: Markup
|
||||||
|
|
||||||
@Prop(Collection(contact.class.Member), contact.string.Members)
|
@Prop(Collection(contact.class.Member), contact.string.Members)
|
||||||
members!: number
|
members!: number
|
||||||
}
|
}
|
||||||
@ -771,6 +777,29 @@ export function createModel (builder: Builder): void {
|
|||||||
filters: []
|
filters: []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(contact.class.Organization, core.class.Class, view.mixin.ObjectPanel, {
|
||||||
|
component: contact.component.EditOrganizationPanel
|
||||||
|
})
|
||||||
|
|
||||||
|
createAction(builder, {
|
||||||
|
label: view.string.Open,
|
||||||
|
icon: view.icon.Open,
|
||||||
|
action: view.actionImpl.ShowPanel,
|
||||||
|
actionProps: {
|
||||||
|
component: contact.component.EditOrganizationPanel,
|
||||||
|
element: 'content'
|
||||||
|
},
|
||||||
|
input: 'focus',
|
||||||
|
category: contact.category.Contact,
|
||||||
|
override: [view.action.Open],
|
||||||
|
keyBinding: ['keyE'],
|
||||||
|
target: contact.class.Organization,
|
||||||
|
context: {
|
||||||
|
mode: ['context', 'browser'],
|
||||||
|
group: 'create'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
builder.mixin(contact.class.Channel, core.class.Class, view.mixin.AttributeFilter, {
|
builder.mixin(contact.class.Channel, core.class.Class, view.mixin.AttributeFilter, {
|
||||||
component: contact.component.ChannelFilter
|
component: contact.component.ChannelFilter
|
||||||
})
|
})
|
||||||
|
@ -70,10 +70,6 @@ export function createReviewModel (builder: Builder): void {
|
|||||||
presenter: recruit.component.OpinionPresenter
|
presenter: recruit.component.OpinionPresenter
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.mixin(recruit.class.Review, core.class.Class, view.mixin.ObjectEditor, {
|
|
||||||
editor: recruit.component.EditReview
|
|
||||||
})
|
|
||||||
|
|
||||||
createAction(builder, {
|
createAction(builder, {
|
||||||
action: view.actionImpl.ShowPopup,
|
action: view.actionImpl.ShowPopup,
|
||||||
actionProps: {
|
actionProps: {
|
||||||
|
@ -14,10 +14,13 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Channel, findContacts, Organization } from '@hcengineering/contact'
|
import { Channel, findContacts, Organization } from '@hcengineering/contact'
|
||||||
import { AttachedData, fillDefaults, generateId, Ref, TxOperations, WithLookup } from '@hcengineering/core'
|
import core, { AttachedData, fillDefaults, generateId, Ref, TxOperations, WithLookup } from '@hcengineering/core'
|
||||||
import { Card, getClient, InlineAttributeBar } from '@hcengineering/presentation'
|
import { Card, getClient, InlineAttributeBar } from '@hcengineering/presentation'
|
||||||
import { Button, createFocusManager, EditBox, FocusHandler, IconInfo, Label } from '@hcengineering/ui'
|
import { Button, createFocusManager, EditBox, FocusHandler, IconAttachment, IconInfo, Label } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { AttachmentPresenter, AttachmentStyledBox } from '@hcengineering/attachment-resources'
|
||||||
|
import { Attachment } from '@hcengineering/attachment'
|
||||||
|
|
||||||
import contact from '../plugin'
|
import contact from '../plugin'
|
||||||
import ChannelsDropdown from './ChannelsDropdown.svelte'
|
import ChannelsDropdown from './ChannelsDropdown.svelte'
|
||||||
import Company from './icons/Company.svelte'
|
import Company from './icons/Company.svelte'
|
||||||
@ -32,7 +35,9 @@
|
|||||||
const id: Ref<Organization> = generateId()
|
const id: Ref<Organization> = generateId()
|
||||||
|
|
||||||
const object: Organization = {
|
const object: Organization = {
|
||||||
name: ''
|
name: '',
|
||||||
|
description: '',
|
||||||
|
attachments: 0
|
||||||
} as unknown as Organization
|
} as unknown as Organization
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
@ -41,8 +46,10 @@
|
|||||||
|
|
||||||
fillDefaults(hierarchy, object, contact.class.Organization)
|
fillDefaults(hierarchy, object, contact.class.Organization)
|
||||||
|
|
||||||
async function createOrganization () {
|
async function createOrganization (): Promise<void> {
|
||||||
await client.createDoc(contact.class.Organization, contact.space.Contacts, object, id)
|
await client.createDoc(contact.class.Organization, contact.space.Contacts, object, id)
|
||||||
|
await descriptionBox.createAttachments(id)
|
||||||
|
|
||||||
for (const channel of channels) {
|
for (const channel of channels) {
|
||||||
await client.addCollection(
|
await client.addCollection(
|
||||||
contact.class.Channel,
|
contact.class.Channel,
|
||||||
@ -69,10 +76,14 @@
|
|||||||
|
|
||||||
let matches: WithLookup<Organization>[] = []
|
let matches: WithLookup<Organization>[] = []
|
||||||
let matchedChannels: AttachedData<Channel>[] = []
|
let matchedChannels: AttachedData<Channel>[] = []
|
||||||
$: findContacts(client, contact.class.Organization, object.name, channels).then((p) => {
|
|
||||||
|
$: void findContacts(client, contact.class.Organization, object.name, channels).then((p) => {
|
||||||
matches = p.contacts as Organization[]
|
matches = p.contacts as Organization[]
|
||||||
matchedChannels = p.channels
|
matchedChannels = p.channels
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let descriptionBox: AttachmentStyledBox
|
||||||
|
let attachments: Map<Ref<Attachment>, Attachment> = new Map<Ref<Attachment>, Attachment>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FocusHandler {manager} />
|
<FocusHandler {manager} />
|
||||||
@ -80,13 +91,14 @@
|
|||||||
<Card
|
<Card
|
||||||
label={contact.string.CreateOrganization}
|
label={contact.string.CreateOrganization}
|
||||||
okAction={createOrganization}
|
okAction={createOrganization}
|
||||||
|
hideAttachments={attachments.size === 0}
|
||||||
canSave={object.name.length > 0}
|
canSave={object.name.length > 0}
|
||||||
on:close={() => {
|
on:close={() => {
|
||||||
dispatch('close')
|
dispatch('close')
|
||||||
}}
|
}}
|
||||||
on:changeContent
|
on:changeContent
|
||||||
>
|
>
|
||||||
<div class="flex-row-center clear-mins">
|
<div class="flex-row-center clear-mins mb-3">
|
||||||
<div class="mr-3">
|
<div class="mr-3">
|
||||||
<Button icon={Company} size={'medium'} kind={'link-bordered'} noFocus />
|
<Button icon={Company} size={'medium'} kind={'link-bordered'} noFocus />
|
||||||
</div>
|
</div>
|
||||||
@ -98,6 +110,29 @@
|
|||||||
focusIndex={1}
|
focusIndex={1}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<AttachmentStyledBox
|
||||||
|
bind:this={descriptionBox}
|
||||||
|
objectId={id}
|
||||||
|
_class={contact.class.Organization}
|
||||||
|
space={contact.space.Contacts}
|
||||||
|
alwaysEdit
|
||||||
|
showButtons={false}
|
||||||
|
bind:content={object.description}
|
||||||
|
placeholder={core.string.Description}
|
||||||
|
kind="indented"
|
||||||
|
isScrollable={false}
|
||||||
|
enableBackReferences={true}
|
||||||
|
enableAttachments={false}
|
||||||
|
on:attachments={(ev) => {
|
||||||
|
if (ev.detail.size > 0) attachments = ev.detail.values
|
||||||
|
else if (ev.detail.size === 0 && ev.detail.values != null) {
|
||||||
|
attachments.clear()
|
||||||
|
attachments = attachments
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<svelte:fragment slot="pool">
|
<svelte:fragment slot="pool">
|
||||||
<ChannelsDropdown
|
<ChannelsDropdown
|
||||||
bind:value={channels}
|
bind:value={channels}
|
||||||
@ -116,7 +151,30 @@
|
|||||||
extraProps={{ showNavigate: false }}
|
extraProps={{ showNavigate: false }}
|
||||||
/>
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<svelte:fragment slot="attachments">
|
||||||
|
{#if attachments.size > 0}
|
||||||
|
{#each Array.from(attachments.values()) as attachment}
|
||||||
|
<AttachmentPresenter
|
||||||
|
value={attachment}
|
||||||
|
showPreview
|
||||||
|
removable
|
||||||
|
on:remove={(result) => {
|
||||||
|
if (result.detail !== undefined) descriptionBox.removeAttachmentById(result.detail._id)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
<svelte:fragment slot="footer">
|
<svelte:fragment slot="footer">
|
||||||
|
<Button
|
||||||
|
icon={IconAttachment}
|
||||||
|
size="large"
|
||||||
|
on:click={() => {
|
||||||
|
descriptionBox.handleAttach()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{#if matches.length > 0}
|
{#if matches.length > 0}
|
||||||
<div class="flex-row-center error-color">
|
<div class="flex-row-center error-color">
|
||||||
<IconInfo size={'small'} />
|
<IconInfo size={'small'} />
|
||||||
|
@ -0,0 +1,163 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { AttachmentStyleBoxCollabEditor } from '@hcengineering/attachment-resources'
|
||||||
|
import core, { Doc, Mixin, Ref } from '@hcengineering/core'
|
||||||
|
import notification from '@hcengineering/notification'
|
||||||
|
import { Panel } from '@hcengineering/panel'
|
||||||
|
import { getResource } from '@hcengineering/platform'
|
||||||
|
import presentation, {
|
||||||
|
type AttributeCategory,
|
||||||
|
createQuery,
|
||||||
|
getClient,
|
||||||
|
type KeyedAttribute
|
||||||
|
} from '@hcengineering/presentation'
|
||||||
|
import { type AnyComponent, Button, Component, IconMixin, IconMoreH, Label } from '@hcengineering/ui'
|
||||||
|
import view from '@hcengineering/view'
|
||||||
|
import {
|
||||||
|
DocAttributeBar,
|
||||||
|
DocNavLink,
|
||||||
|
getCollectionCounter,
|
||||||
|
getDocAttrsInfo,
|
||||||
|
getDocMixins,
|
||||||
|
showMenu
|
||||||
|
} from '@hcengineering/view-resources'
|
||||||
|
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||||
|
import { Organization } from '@hcengineering/contact'
|
||||||
|
|
||||||
|
import contact from '../plugin'
|
||||||
|
import EditOrganization from './EditOrganization.svelte'
|
||||||
|
|
||||||
|
export let _id: Ref<Organization>
|
||||||
|
export let embedded: boolean = false
|
||||||
|
export let readonly: boolean = false
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
|
const query = createQuery()
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const inboxClient = getResource(notification.function.GetInboxNotificationsClient).then((res) => res())
|
||||||
|
|
||||||
|
const ignoreKeys = ['comments', 'name', 'channels', 'description', 'attachments']
|
||||||
|
|
||||||
|
let object: Organization | undefined = undefined
|
||||||
|
let lastId: Ref<Organization> | undefined = undefined
|
||||||
|
|
||||||
|
let mixins: Mixin<Doc>[] = []
|
||||||
|
let editors: Array<{ key: KeyedAttribute, editor: AnyComponent, category: AttributeCategory }> = []
|
||||||
|
|
||||||
|
let showAllMixins = false
|
||||||
|
let saved = false
|
||||||
|
|
||||||
|
$: mixins = object ? getDocMixins(object, showAllMixins) : []
|
||||||
|
|
||||||
|
$: descriptionKey = client.getHierarchy().getAttribute(contact.class.Organization, 'description')
|
||||||
|
|
||||||
|
$: getDocAttrsInfo(mixins, ignoreKeys, contact.class.Organization).then((res) => {
|
||||||
|
editors = res.editors
|
||||||
|
})
|
||||||
|
|
||||||
|
$: updateObject(_id)
|
||||||
|
|
||||||
|
function updateObject (_id: Ref<Organization>): void {
|
||||||
|
if (lastId !== _id) {
|
||||||
|
const prev = lastId
|
||||||
|
lastId = _id
|
||||||
|
if (prev !== undefined) {
|
||||||
|
void inboxClient.then((client) => client.readDoc(getClient(), prev))
|
||||||
|
}
|
||||||
|
query.query(contact.class.Organization, { _id }, (result) => {
|
||||||
|
object = result[0]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(async () => {
|
||||||
|
void inboxClient.then((client) => client.readDoc(getClient(), _id))
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if object}
|
||||||
|
<Panel
|
||||||
|
isHeader={false}
|
||||||
|
isSub={false}
|
||||||
|
isAside={true}
|
||||||
|
{embedded}
|
||||||
|
{object}
|
||||||
|
on:open
|
||||||
|
on:close={() => {
|
||||||
|
dispatch('close')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="title">
|
||||||
|
<DocNavLink noUnderline {object}>
|
||||||
|
<div class="title">{object.name}</div>
|
||||||
|
</DocNavLink>
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<svelte:fragment slot="attributes" let:direction={dir}>
|
||||||
|
{#if dir === 'column'}
|
||||||
|
<DocAttributeBar {object} {mixins} {ignoreKeys} />
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<svelte:fragment slot="pre-utils">
|
||||||
|
{#if saved}
|
||||||
|
<Label label={presentation.string.Saved} />
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<svelte:fragment slot="utils">
|
||||||
|
<Button
|
||||||
|
icon={IconMoreH}
|
||||||
|
iconProps={{ size: 'medium' }}
|
||||||
|
kind={'icon'}
|
||||||
|
on:click={(e) => {
|
||||||
|
showMenu(e, { object, excludedActions: [view.action.Open] })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon={IconMixin}
|
||||||
|
kind={'icon'}
|
||||||
|
iconProps={{ size: 'medium' }}
|
||||||
|
selected={showAllMixins}
|
||||||
|
on:click={() => {
|
||||||
|
showAllMixins = !showAllMixins
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<div class="flex-col flex-grow flex-no-shrink step-tb-6">
|
||||||
|
<EditOrganization {object} />
|
||||||
|
<div class="flex-col flex-grow w-full mt-6 relative">
|
||||||
|
<AttachmentStyleBoxCollabEditor
|
||||||
|
focusIndex={30}
|
||||||
|
{object}
|
||||||
|
key={{ key: 'description', attr: descriptionKey }}
|
||||||
|
placeholder={core.string.Description}
|
||||||
|
on:saved={(evt) => {
|
||||||
|
saved = evt.detail
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#each editors as editor}
|
||||||
|
{#if editor.editor}
|
||||||
|
<div class="step-tb-6">
|
||||||
|
<Component
|
||||||
|
is={editor.editor}
|
||||||
|
props={{
|
||||||
|
objectId: object._id,
|
||||||
|
_class: editor.key.attr.attributeOf,
|
||||||
|
object,
|
||||||
|
space: object.space,
|
||||||
|
key: editor.key,
|
||||||
|
readonly,
|
||||||
|
[editor.key.key]: getCollectionCounter(hierarchy, object, editor.key)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</Panel>
|
||||||
|
{/if}
|
@ -18,6 +18,8 @@
|
|||||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||||
import { tooltip } from '@hcengineering/ui'
|
import { tooltip } from '@hcengineering/ui'
|
||||||
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
|
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
|
||||||
|
|
||||||
|
import contact from '../plugin'
|
||||||
import Company from './icons/Company.svelte'
|
import Company from './icons/Company.svelte'
|
||||||
|
|
||||||
export let value: Organization
|
export let value: Organization
|
||||||
@ -30,9 +32,15 @@
|
|||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
{#if inline}
|
{#if inline}
|
||||||
<ObjectMention object={value} {disabled} {accent} {noUnderline} />
|
<ObjectMention
|
||||||
|
object={value}
|
||||||
|
{disabled}
|
||||||
|
{accent}
|
||||||
|
{noUnderline}
|
||||||
|
component={contact.component.EditOrganizationPanel}
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<DocNavLink {disabled} object={value} {accent} {noUnderline}>
|
<DocNavLink {disabled} object={value} {accent} {noUnderline} component={contact.component.EditOrganizationPanel}>
|
||||||
<div class="flex-presenter" style:max-width={maxWidth} use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
|
<div class="flex-presenter" style:max-width={maxWidth} use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
|
||||||
<div class="icon circle">
|
<div class="icon circle">
|
||||||
<Company size={'small'} />
|
<Company size={'small'} />
|
||||||
|
@ -103,6 +103,7 @@ import UsersList from './components/UsersList.svelte'
|
|||||||
import SelectUsersPopup from './components/SelectUsersPopup.svelte'
|
import SelectUsersPopup from './components/SelectUsersPopup.svelte'
|
||||||
import IconAddMember from './components/icons/AddMember.svelte'
|
import IconAddMember from './components/icons/AddMember.svelte'
|
||||||
import UserDetails from './components/UserDetails.svelte'
|
import UserDetails from './components/UserDetails.svelte'
|
||||||
|
import EditOrganizationPanel from './components/EditOrganizationPanel.svelte'
|
||||||
|
|
||||||
import contact from './plugin'
|
import contact from './plugin'
|
||||||
import {
|
import {
|
||||||
@ -332,7 +333,8 @@ export default async (): Promise<Resources> => ({
|
|||||||
PersonAccountFilterValuePresenter,
|
PersonAccountFilterValuePresenter,
|
||||||
DeleteConfirmationPopup,
|
DeleteConfirmationPopup,
|
||||||
PersonAccountRefPresenter,
|
PersonAccountRefPresenter,
|
||||||
PersonIcon
|
PersonIcon,
|
||||||
|
EditOrganizationPanel
|
||||||
},
|
},
|
||||||
completion: {
|
completion: {
|
||||||
EmployeeQuery: async (
|
EmployeeQuery: async (
|
||||||
|
@ -37,7 +37,8 @@ import {
|
|||||||
type Timestamp,
|
type Timestamp,
|
||||||
type TxOperations,
|
type TxOperations,
|
||||||
getCurrentAccount,
|
getCurrentAccount,
|
||||||
toIdMap
|
toIdMap,
|
||||||
|
type Class
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
||||||
import { getEmbeddedLabel, getResource } from '@hcengineering/platform'
|
import { getEmbeddedLabel, getResource } from '@hcengineering/platform'
|
||||||
@ -268,14 +269,18 @@ async function generateLocation (loc: Location, id: Ref<Contact>): Promise<Resol
|
|||||||
: client.getHierarchy().isDerived(doc._class, contact.mixin.Employee)
|
: client.getHierarchy().isDerived(doc._class, contact.mixin.Employee)
|
||||||
? 'employees'
|
? 'employees'
|
||||||
: 'persons'
|
: 'persons'
|
||||||
|
|
||||||
|
const objectPanel = client.getHierarchy().classHierarchyMixin(doc._class as Ref<Class<Doc>>, view.mixin.ObjectPanel)
|
||||||
|
const component = objectPanel?.component ?? view.component.EditDoc
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loc: {
|
loc: {
|
||||||
path: [appComponent, workspace],
|
path: [appComponent, workspace],
|
||||||
fragment: getPanelURI(view.component.EditDoc, doc._id, doc._class, 'content')
|
fragment: getPanelURI(component, doc._id, doc._class, 'content')
|
||||||
},
|
},
|
||||||
defaultLocation: {
|
defaultLocation: {
|
||||||
path: [appComponent, workspace, contactId, special],
|
path: [appComponent, workspace, contactId, special],
|
||||||
fragment: getPanelURI(view.component.EditDoc, doc._id, doc._class, 'content')
|
fragment: getPanelURI(component, doc._id, doc._class, 'content')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,6 +121,7 @@ export interface Member extends AttachedDoc {
|
|||||||
*/
|
*/
|
||||||
export interface Organization extends Contact {
|
export interface Organization extends Contact {
|
||||||
members: number
|
members: number
|
||||||
|
description?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -196,7 +197,8 @@ export const contactPlugin = plugin(contactId, {
|
|||||||
SpaceMembers: '' as AnyComponent,
|
SpaceMembers: '' as AnyComponent,
|
||||||
DeleteConfirmationPopup: '' as AnyComponent,
|
DeleteConfirmationPopup: '' as AnyComponent,
|
||||||
AccountArrayEditor: '' as AnyComponent,
|
AccountArrayEditor: '' as AnyComponent,
|
||||||
PersonIcon: '' as AnyComponent
|
PersonIcon: '' as AnyComponent,
|
||||||
|
EditOrganizationPanel: '' as AnyComponent
|
||||||
},
|
},
|
||||||
channelProvider: {
|
channelProvider: {
|
||||||
Email: '' as Ref<ChannelProvider>,
|
Email: '' as Ref<ChannelProvider>,
|
||||||
|
@ -72,7 +72,8 @@ async function generateIdLocation (loc: Location, shortLink: string): Promise<Re
|
|||||||
}
|
}
|
||||||
const appComponent = loc.path[0] ?? ''
|
const appComponent = loc.path[0] ?? ''
|
||||||
const workspace = loc.path[1] ?? ''
|
const workspace = loc.path[1] ?? ''
|
||||||
const objectPanel = hierarchy.classHierarchyMixin(recruit.mixin.Candidate as Ref<Class<Doc>>, view.mixin.ObjectPanel)
|
const objectPanel = hierarchy.classHierarchyMixin(Hierarchy.mixinOrClass(doc), view.mixin.ObjectPanel)
|
||||||
|
|
||||||
const component = objectPanel?.component ?? view.component.EditDoc
|
const component = objectPanel?.component ?? view.component.EditDoc
|
||||||
const special = _class === recruit.mixin.Candidate ? 'talents' : 'organizations'
|
const special = _class === recruit.mixin.Candidate ? 'talents' : 'organizations'
|
||||||
const defaultPath = [appComponent, workspace, recruitId, special]
|
const defaultPath = [appComponent, workspace, recruitId, special]
|
||||||
|
@ -21,11 +21,9 @@
|
|||||||
import {
|
import {
|
||||||
ActionContext,
|
ActionContext,
|
||||||
AttributeCategory,
|
AttributeCategory,
|
||||||
AttributeCategoryOrder,
|
|
||||||
AttributesBar,
|
AttributesBar,
|
||||||
KeyedAttribute,
|
KeyedAttribute,
|
||||||
createQuery,
|
createQuery,
|
||||||
getAttributePresenterClass,
|
|
||||||
getClient,
|
getClient,
|
||||||
hasResource
|
hasResource
|
||||||
} from '@hcengineering/presentation'
|
} from '@hcengineering/presentation'
|
||||||
@ -33,8 +31,8 @@
|
|||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||||
|
|
||||||
import { DocNavLink, ParentsNavigator, getDocLabel, getDocMixins, showMenu } from '..'
|
import { DocNavLink, ParentsNavigator, getDocLabel, getDocMixins, showMenu, getDocAttrsInfo } from '..'
|
||||||
import { categorizeFields, getCollectionCounter, getFiltredKeys } from '../utils'
|
import { getCollectionCounter } from '../utils'
|
||||||
import DocAttributeBar from './DocAttributeBar.svelte'
|
import DocAttributeBar from './DocAttributeBar.svelte'
|
||||||
|
|
||||||
export let _id: Ref<Doc>
|
export let _id: Ref<Doc>
|
||||||
@ -96,8 +94,6 @@
|
|||||||
let mixins: Array<Mixin<Doc>> = []
|
let mixins: Array<Mixin<Doc>> = []
|
||||||
let showAllMixins = false
|
let showAllMixins = false
|
||||||
|
|
||||||
$: mixins = getDocMixins(object, showAllMixins, ignoreMixins, realObjectClass)
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let ignoreKeys: string[] = []
|
let ignoreKeys: string[] = []
|
||||||
@ -107,33 +103,14 @@
|
|||||||
let inplaceAttributes: string[] = []
|
let inplaceAttributes: string[] = []
|
||||||
let ignoreMixins: Set<Ref<Mixin<Doc>>> = new Set<Ref<Mixin<Doc>>>()
|
let ignoreMixins: Set<Ref<Mixin<Doc>>> = new Set<Ref<Mixin<Doc>>>()
|
||||||
|
|
||||||
|
$: mixins = getDocMixins(object, showAllMixins, ignoreMixins, realObjectClass)
|
||||||
|
|
||||||
async function updateKeys (): Promise<void> {
|
async function updateKeys (): Promise<void> {
|
||||||
const keysMap = new Map(getFiltredKeys(hierarchy, realObjectClass, ignoreKeys).map((p) => [p.attr._id, p]))
|
const info = await getDocAttrsInfo(mixins, ignoreKeys, realObjectClass, allowedCollections, collectionArrays)
|
||||||
for (const m of mixins) {
|
|
||||||
const mkeys = getFiltredKeys(hierarchy, m._id, ignoreKeys)
|
|
||||||
for (const key of mkeys) {
|
|
||||||
keysMap.set(key.attr._id, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const filtredKeys = Array.from(keysMap.values())
|
|
||||||
const { attributes, collections } = categorizeFields(hierarchy, filtredKeys, collectionArrays, allowedCollections)
|
|
||||||
|
|
||||||
keys = attributes.map((it) => it.key)
|
keys = info.keys
|
||||||
|
inplaceAttributes = info.inplaceAttributes
|
||||||
const editors: Array<{ key: KeyedAttribute, editor: AnyComponent, category: AttributeCategory }> = []
|
fieldEditors = info.editors
|
||||||
const newInplaceAttributes: string[] = []
|
|
||||||
|
|
||||||
for (const k of collections) {
|
|
||||||
if (allowedCollections.includes(k.key.key)) continue
|
|
||||||
const editor = await getFieldEditor(k.key)
|
|
||||||
if (editor === undefined) continue
|
|
||||||
if (k.category === 'inplace') {
|
|
||||||
newInplaceAttributes.push(k.key.key)
|
|
||||||
}
|
|
||||||
editors.push({ key: k.key, editor, category: k.category })
|
|
||||||
}
|
|
||||||
inplaceAttributes = newInplaceAttributes
|
|
||||||
fieldEditors = editors.sort((a, b) => AttributeCategoryOrder[a.category] - AttributeCategoryOrder[b.category])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MixinEditor {
|
interface MixinEditor {
|
||||||
@ -166,37 +143,18 @@
|
|||||||
|
|
||||||
$: editorFooter = getEditorFooter(_class, object)
|
$: editorFooter = getEditorFooter(_class, object)
|
||||||
|
|
||||||
$: getEditorOrDefault(realObjectClass, _id)
|
$: void getEditorOrDefault(realObjectClass, _id)
|
||||||
|
|
||||||
async function getEditorOrDefault (_class: Ref<Class<Doc>>, _id: Ref<Doc>): Promise<void> {
|
async function getEditorOrDefault (_class: Ref<Class<Doc>>, _id: Ref<Doc>): Promise<void> {
|
||||||
await updateKeys()
|
await updateKeys()
|
||||||
mainEditor = getEditor(_class)
|
mainEditor = getEditor(_class)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getFieldEditor (key: KeyedAttribute): Promise<AnyComponent | undefined> {
|
|
||||||
const attrClass = getAttributePresenterClass(hierarchy, key.attr)
|
|
||||||
const clazz = hierarchy.getClass(attrClass.attrClass)
|
|
||||||
const mix = {
|
|
||||||
array: view.mixin.ArrayEditor,
|
|
||||||
collection: view.mixin.CollectionEditor,
|
|
||||||
inplace: view.mixin.InlineAttributEditor,
|
|
||||||
attribute: view.mixin.AttributeEditor,
|
|
||||||
object: undefined
|
|
||||||
}
|
|
||||||
const mixinRef = mix[attrClass.category]
|
|
||||||
if (mixinRef) {
|
|
||||||
const editorMixin = hierarchy.as(clazz, mixinRef)
|
|
||||||
return (editorMixin as any).editor
|
|
||||||
} else {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let title: string | undefined = undefined
|
let title: string | undefined = undefined
|
||||||
let rawTitle: string = ''
|
let rawTitle: string = ''
|
||||||
|
|
||||||
$: if (object !== undefined) {
|
$: if (object !== undefined) {
|
||||||
getDocLabel(pClient, object).then((t) => {
|
void getDocLabel(pClient, object).then((t) => {
|
||||||
if (t) {
|
if (t) {
|
||||||
rawTitle = t
|
rawTitle = t
|
||||||
}
|
}
|
||||||
@ -216,7 +174,7 @@
|
|||||||
let headerLoading = false
|
let headerLoading = false
|
||||||
$: {
|
$: {
|
||||||
headerLoading = true
|
headerLoading = true
|
||||||
getHeaderEditor(realObjectClass).then((r) => {
|
void getHeaderEditor(realObjectClass).then((r) => {
|
||||||
headerEditor = r
|
headerEditor = r
|
||||||
headerLoading = false
|
headerLoading = false
|
||||||
})
|
})
|
||||||
@ -236,7 +194,7 @@
|
|||||||
collectionArrays = ev.detail.collectionArrays ?? []
|
collectionArrays = ev.detail.collectionArrays ?? []
|
||||||
title = ev.detail.title
|
title = ev.detail.title
|
||||||
mixins = getDocMixins(object, showAllMixins, ignoreMixins, realObjectClass)
|
mixins = getDocMixins(object, showAllMixins, ignoreMixins, realObjectClass)
|
||||||
updateKeys()
|
void updateKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
$: finalTitle = title ?? rawTitle
|
$: finalTitle = title ?? rawTitle
|
||||||
|
@ -53,12 +53,14 @@ import { type Restrictions } from '@hcengineering/guest'
|
|||||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||||
import { getResource, translate } from '@hcengineering/platform'
|
import { getResource, translate } from '@hcengineering/platform'
|
||||||
import {
|
import {
|
||||||
|
type AttributeCategory,
|
||||||
|
AttributeCategoryOrder,
|
||||||
getAttributePresenterClass,
|
getAttributePresenterClass,
|
||||||
getClient,
|
getClient,
|
||||||
hasResource,
|
hasResource,
|
||||||
isAdminUser,
|
type KeyedAttribute,
|
||||||
type AttributeCategory,
|
getFiltredKeys,
|
||||||
type KeyedAttribute
|
isAdminUser
|
||||||
} from '@hcengineering/presentation'
|
} from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
ErrorPresenter,
|
ErrorPresenter,
|
||||||
@ -1266,3 +1268,67 @@ export const restrictionStore = writable<Restrictions>({
|
|||||||
disableNavigation: false,
|
disableNavigation: false,
|
||||||
disableActions: false
|
disableActions: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export async function getDocAttrsInfo (
|
||||||
|
mixins: Array<Mixin<Doc>>,
|
||||||
|
ignoreKeys: string[],
|
||||||
|
_class: Ref<Class<Doc>>,
|
||||||
|
allowedCollections: string[] = [],
|
||||||
|
collectionArrays: string[] = []
|
||||||
|
): Promise<{
|
||||||
|
keys: KeyedAttribute[]
|
||||||
|
inplaceAttributes: string[]
|
||||||
|
editors: Array<{ key: KeyedAttribute, editor: AnyComponent, category: AttributeCategory }>
|
||||||
|
}> {
|
||||||
|
const client = getClient()
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
|
|
||||||
|
const keysMap = new Map(getFiltredKeys(hierarchy, _class, ignoreKeys).map((p) => [p.attr._id, p]))
|
||||||
|
for (const m of mixins) {
|
||||||
|
const mkeys = getFiltredKeys(hierarchy, m._id, ignoreKeys)
|
||||||
|
for (const key of mkeys) {
|
||||||
|
keysMap.set(key.attr._id, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const filteredKeys = Array.from(keysMap.values())
|
||||||
|
const { attributes, collections } = categorizeFields(hierarchy, filteredKeys, collectionArrays, allowedCollections)
|
||||||
|
|
||||||
|
const keys = attributes.map((it) => it.key)
|
||||||
|
const editors: Array<{ key: KeyedAttribute, editor: AnyComponent, category: AttributeCategory }> = []
|
||||||
|
const inplaceAttributes: string[] = []
|
||||||
|
|
||||||
|
for (const k of collections) {
|
||||||
|
if (allowedCollections.includes(k.key.key)) continue
|
||||||
|
const editor = await getAttrEditor(k.key, hierarchy)
|
||||||
|
if (editor === undefined) continue
|
||||||
|
if (k.category === 'inplace') {
|
||||||
|
inplaceAttributes.push(k.key.key)
|
||||||
|
}
|
||||||
|
editors.push({ key: k.key, editor, category: k.category })
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
keys,
|
||||||
|
inplaceAttributes,
|
||||||
|
editors: editors.sort((a, b) => AttributeCategoryOrder[a.category] - AttributeCategoryOrder[b.category])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAttrEditor (key: KeyedAttribute, hierarchy: Hierarchy): Promise<AnyComponent | undefined> {
|
||||||
|
const attrClass = getAttributePresenterClass(hierarchy, key.attr)
|
||||||
|
const clazz = hierarchy.getClass(attrClass.attrClass)
|
||||||
|
const mix = {
|
||||||
|
array: view.mixin.ArrayEditor,
|
||||||
|
collection: view.mixin.CollectionEditor,
|
||||||
|
inplace: view.mixin.InlineAttributEditor,
|
||||||
|
attribute: view.mixin.AttributeEditor,
|
||||||
|
object: undefined as any
|
||||||
|
}
|
||||||
|
const mixinRef = mix[attrClass.category]
|
||||||
|
if (mixinRef !== undefined) {
|
||||||
|
const editorMixin = hierarchy.as(clazz, mixinRef)
|
||||||
|
return (editorMixin as any).editor
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user