mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
parent
987a8d670a
commit
d555409ca1
@ -71,13 +71,14 @@ export class TContact extends TDoc implements Contact {
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), 'Comments' as IntlString)
|
||||
comments?: number
|
||||
|
||||
@Prop(TypeString(), 'Location' as IntlString)
|
||||
city!: string
|
||||
}
|
||||
|
||||
@Model(contact.class.Person, contact.class.Contact)
|
||||
@UX('Person' as IntlString, contact.icon.Person, undefined, 'name')
|
||||
export class TPerson extends TContact implements Person {
|
||||
@Prop(TypeString(), 'City' as IntlString)
|
||||
city!: string
|
||||
}
|
||||
|
||||
@Model(contact.class.Organization, contact.class.Contact)
|
||||
@ -85,6 +86,7 @@ export class TPerson extends TContact implements Person {
|
||||
export class TOrganization extends TContact implements Organization {}
|
||||
|
||||
@Model(contact.class.Employee, contact.class.Person)
|
||||
@UX('Employee' as IntlString, contact.icon.Person)
|
||||
export class TEmployee extends TPerson implements Employee {}
|
||||
|
||||
@Model(contact.class.EmployeeAccount, core.class.Account)
|
||||
@ -114,44 +116,23 @@ export function createModel (builder: Builder): void {
|
||||
TEmployeeAccount
|
||||
)
|
||||
|
||||
builder.mixin(contact.class.Persons, core.class.Class, workbench.mixin.SpaceView, {
|
||||
view: {
|
||||
class: contact.class.Person,
|
||||
createItemDialog: contact.component.CreatePerson
|
||||
}
|
||||
builder.mixin(contact.class.Person, core.class.Class, view.mixin.ObjectFactory, {
|
||||
component: contact.component.CreatePerson
|
||||
})
|
||||
|
||||
builder.mixin(contact.class.Organizations, core.class.Class, workbench.mixin.SpaceView, {
|
||||
view: {
|
||||
class: contact.class.Organization,
|
||||
createItemDialog: contact.component.CreateOrganization
|
||||
}
|
||||
builder.mixin(contact.class.Organization, core.class.Class, view.mixin.ObjectFactory, {
|
||||
component: contact.component.CreateOrganization
|
||||
})
|
||||
|
||||
builder.createDoc(workbench.class.Application, core.space.Model, {
|
||||
label: contact.string.Contacts,
|
||||
icon: contact.icon.Person,
|
||||
hidden: false,
|
||||
navigatorModel: {
|
||||
spaces: [
|
||||
{
|
||||
label: contact.string.Persons,
|
||||
spaceClass: contact.class.Persons,
|
||||
addSpaceLabel: contact.string.CreatePersons,
|
||||
createComponent: contact.component.CreatePersons
|
||||
},
|
||||
{
|
||||
label: contact.string.Organizations,
|
||||
spaceClass: contact.class.Organizations,
|
||||
addSpaceLabel: contact.string.CreateOrganizations,
|
||||
createComponent: contact.component.CreateOrganizations
|
||||
}
|
||||
]
|
||||
}
|
||||
component: contact.component.Contacts
|
||||
}, contact.app.Contacts)
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: contact.class.Person,
|
||||
attachTo: contact.class.Contact,
|
||||
descriptor: view.viewlet.Table,
|
||||
open: contact.component.EditContact,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
@ -161,19 +142,11 @@ export function createModel (builder: Builder): void {
|
||||
'city',
|
||||
{ presenter: attachment.component.AttachmentsPresenter, label: 'Files', sortingKey: 'attachments' },
|
||||
'modifiedOn',
|
||||
{ presenter: contact.component.RolePresenter, label: 'Role' },
|
||||
'channels'
|
||||
]
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: contact.class.Organization,
|
||||
descriptor: view.viewlet.Table,
|
||||
open: contact.component.EditContact,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
options: {},
|
||||
config: ['', { presenter: attachment.component.AttachmentsPresenter, label: 'Files', sortingKey: 'attachments' }, 'modifiedOn', 'channels']
|
||||
})
|
||||
|
||||
builder.mixin(contact.class.Person, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: contact.component.EditPerson
|
||||
})
|
||||
|
@ -36,7 +36,9 @@ export const ids = mergeIds(contactId, contact, {
|
||||
CreateOrganization: '' as AnyComponent,
|
||||
CreatePersons: '' as AnyComponent,
|
||||
CreateOrganizations: '' as AnyComponent,
|
||||
OrganizationPresenter: '' as AnyComponent
|
||||
OrganizationPresenter: '' as AnyComponent,
|
||||
Contacts: '' as AnyComponent,
|
||||
RolePresenter: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
Organizations: '' as IntlString,
|
||||
|
@ -100,6 +100,10 @@ export function createModel (builder: Builder): void {
|
||||
editor: recruit.component.Applications
|
||||
})
|
||||
|
||||
builder.mixin(recruit.mixin.Candidate, core.class.Mixin, view.mixin.ObjectFactory, {
|
||||
component: recruit.component.CreateCandidate
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
core.space.Model,
|
||||
@ -235,10 +239,6 @@ export function createModel (builder: Builder): void {
|
||||
card: recruit.component.KanbanCard
|
||||
})
|
||||
|
||||
builder.mixin(recruit.mixin.Candidate, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: recruit.component.EditCandidate
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: recruit.component.EditApplication
|
||||
})
|
||||
|
@ -44,7 +44,6 @@ export default mergeIds(recruitId, recruit, {
|
||||
component: {
|
||||
CreateVacancy: '' as AnyComponent,
|
||||
CreateApplication: '' as AnyComponent,
|
||||
EditCandidate: '' as AnyComponent,
|
||||
KanbanCard: '' as AnyComponent,
|
||||
ApplicationPresenter: '' as AnyComponent,
|
||||
ApplicationsPresenter: '' as AnyComponent,
|
||||
@ -52,7 +51,8 @@ export default mergeIds(recruitId, recruit, {
|
||||
EditApplication: '' as AnyComponent,
|
||||
TemplatesIcon: '' as AnyComponent,
|
||||
Applications: '' as AnyComponent,
|
||||
Candidates: '' as AnyComponent
|
||||
Candidates: '' as AnyComponent,
|
||||
CreateCandidate: '' as AnyComponent
|
||||
},
|
||||
template: {
|
||||
DefaultVacancy: '' as Ref<KanbanTemplate>
|
||||
|
@ -19,7 +19,7 @@ import { Builder, Mixin, Model } from '@anticrm/model'
|
||||
import core, { TClass, TDoc } from '@anticrm/model-core'
|
||||
import type { Asset, IntlString, Resource, Status } from '@anticrm/platform'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import type { Action, ActionTarget, AttributeEditor, AttributePresenter, ObjectEditor, ObjectValidator, Viewlet, ViewletDescriptor } from '@anticrm/view'
|
||||
import type { Action, ActionTarget, AttributeEditor, AttributePresenter, ObjectEditor, ObjectFactory, ObjectValidator, Viewlet, ViewletDescriptor } from '@anticrm/view'
|
||||
import view from './plugin'
|
||||
|
||||
@Mixin(view.mixin.AttributeEditor, core.class.Class)
|
||||
@ -42,6 +42,11 @@ export class TObjectValidator extends TClass implements ObjectValidator {
|
||||
validator!: Resource<(<T extends Doc>(doc: T, client: Client) => Promise<Status<{}>>)>
|
||||
}
|
||||
|
||||
@Mixin(view.mixin.ObjectFactory, core.class.Class)
|
||||
export class TObjectFactory extends TClass implements ObjectFactory {
|
||||
component!: AnyComponent
|
||||
}
|
||||
|
||||
@Model(view.class.ViewletDescriptor, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TViewletDescriptor extends TDoc implements ViewletDescriptor {
|
||||
component!: AnyComponent
|
||||
@ -70,7 +75,7 @@ export class TActionTarget extends TDoc implements ActionTarget {
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TAttributeEditor, TAttributePresenter, TObjectEditor, TViewletDescriptor, TViewlet, TAction, TActionTarget, TObjectValidator)
|
||||
builder.createModel(TAttributeEditor, TAttributePresenter, TObjectEditor, TViewletDescriptor, TViewlet, TAction, TActionTarget, TObjectValidator, TObjectFactory)
|
||||
|
||||
builder.mixin(core.class.TypeString, core.class.Class, view.mixin.AttributeEditor, {
|
||||
editor: view.component.StringEditor
|
||||
|
@ -157,7 +157,7 @@ export class Hierarchy {
|
||||
}
|
||||
|
||||
private txMixin (tx: TxMixin<Doc, Doc>): void {
|
||||
if (tx.objectClass === core.class.Class) {
|
||||
if (this.isDerived(tx.objectClass, core.class.Class)) {
|
||||
const obj = this.getClass(tx.objectId as Ref<Class<Obj>>) as any
|
||||
TxProcessor.updateMixin4Doc(obj, tx.mixin, tx.attributes)
|
||||
}
|
||||
@ -304,7 +304,14 @@ export class Hierarchy {
|
||||
const result = new Map<string, AnyAttribute>()
|
||||
let ancestors = this.getAncestors(clazz)
|
||||
if (to !== undefined) {
|
||||
ancestors = ancestors.filter(c => this.isDerived(c, to) && c !== to)
|
||||
const toAncestors = this.getAncestors(to)
|
||||
for (const uto of toAncestors) {
|
||||
if (ancestors.includes(uto)) {
|
||||
to = uto
|
||||
break
|
||||
}
|
||||
}
|
||||
ancestors = ancestors.filter(c => this.isDerived(c, to as Ref<Class<Doc>>) && c !== to)
|
||||
}
|
||||
|
||||
for (const cls of ancestors) {
|
||||
|
@ -14,27 +14,22 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Asset,IntlString } from '@anticrm/platform'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { AnySvelteComponent } from '../types'
|
||||
import { Action } from '../types'
|
||||
import Icon from './Icon.svelte'
|
||||
import Label from './Label.svelte'
|
||||
|
||||
export let actions: {
|
||||
label: IntlString
|
||||
icon?: Asset | AnySvelteComponent
|
||||
action: (ctx?: any) => void | Promise<void>
|
||||
}[] = []
|
||||
export let actions: Action[] = []
|
||||
export let ctx: any = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div class="flex-col popup">
|
||||
{#each actions as action}
|
||||
<div class="flex-row-center menu-item" on:click={() => {
|
||||
<div class="flex-row-center menu-item" on:click={() => {
|
||||
dispatch('close')
|
||||
action.action(ctx)
|
||||
action.action(ctx)
|
||||
}}>
|
||||
{#if action.icon}
|
||||
<Icon icon={action.icon} size={'small'} />
|
||||
|
@ -13,6 +13,7 @@
|
||||
"PersonsFolder": "Persons folder",
|
||||
"AddSocialLinks": "Add social links",
|
||||
"MakePrivate": "Make private",
|
||||
"MakePrivateDescription": "Only members can see it"
|
||||
"MakePrivateDescription": "Only members can see it",
|
||||
"Create": "Contact"
|
||||
}
|
||||
}
|
@ -39,6 +39,7 @@
|
||||
"@anticrm/core": "~0.6.11",
|
||||
"@anticrm/view": "~0.6.0",
|
||||
"@anticrm/attachment-resources": "~0.6.0",
|
||||
"@anticrm/panel": "~0.6.0"
|
||||
"@anticrm/panel": "~0.6.0",
|
||||
"@anticrm/view-resources": "~0.6.0"
|
||||
}
|
||||
}
|
||||
|
122
plugins/contact-resources/src/components/Contacts.svelte
Normal file
122
plugins/contact-resources/src/components/Contacts.svelte
Normal file
@ -0,0 +1,122 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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 { getClient } from '@anticrm/presentation'
|
||||
import { Button, EditWithIcon, Icon, IconAdd, IconSearch, Label, ScrollBox, showPopup } from '@anticrm/ui'
|
||||
import view, { Viewlet } from '@anticrm/view'
|
||||
import { Table } from '@anticrm/view-resources'
|
||||
import contact from '../plugin'
|
||||
import CreateContact from './CreateContact.svelte'
|
||||
|
||||
let search = ''
|
||||
$: resultQuery = search === '' ? { } : { $search: search }
|
||||
|
||||
const client = getClient()
|
||||
const tableDescriptor = client.findOne<Viewlet>(view.class.Viewlet, { attachTo: contact.class.Contact, descriptor: view.viewlet.Table })
|
||||
|
||||
|
||||
function showCreateDialog (ev: Event) {
|
||||
showPopup(CreateContact, { space: contact.space.Contacts, targetElement: ev.target }, ev.target as HTMLElement)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="contacts-header-container">
|
||||
<div class="header-container">
|
||||
<div class="flex-row-center">
|
||||
<span class="icon"><Icon icon={contact.icon.Person} size={'small'}/></span>
|
||||
<span class="label"><Label label={contact.string.Contacts}/></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditWithIcon icon={IconSearch} placeholder={'Search'} bind:value={search} on:change={() => { resultQuery = {} } } />
|
||||
<Button icon={IconAdd} label={contact.string.Create} primary={true} size={'small'} on:click={(ev) => showCreateDialog(ev)}/>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="panel-component">
|
||||
<ScrollBox vertical stretch noShift>
|
||||
{#await tableDescriptor then descr}
|
||||
{#if descr}
|
||||
<Table
|
||||
_class={contact.class.Contact}
|
||||
config={descr.config}
|
||||
options={descr.options}
|
||||
query={ resultQuery }
|
||||
enableChecking
|
||||
/>
|
||||
{/if}
|
||||
{/await}
|
||||
</ScrollBox>
|
||||
</div>
|
||||
</div>
|
||||
<style lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding-bottom: 1.25rem;
|
||||
margin-top: 2rem;
|
||||
|
||||
.panel-component {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: 1rem;
|
||||
height: 100%;
|
||||
border-radius: 1.25rem;
|
||||
background-color: var(--theme-bg-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
.contacts-header-container {
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: min-content;
|
||||
gap: .75rem;
|
||||
align-items: center;
|
||||
padding: 0 1.75rem 0 2.5rem;
|
||||
height: 4rem;
|
||||
min-height: 4rem;
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
|
||||
.icon {
|
||||
margin-right: .5rem;
|
||||
opacity: .6;
|
||||
}
|
||||
.label, .description {
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 35rem;
|
||||
}
|
||||
.label {
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
.description {
|
||||
font-size: .75rem;
|
||||
color: var(--theme-content-trans-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,32 @@
|
||||
<script lang='ts'>
|
||||
import { Asset } from '@anticrm/platform'
|
||||
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Menu, Action, showPopup, closePopup } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import contact from '../plugin'
|
||||
|
||||
export let targetElement: HTMLElement
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const actions: Action[] = []
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
client.getHierarchy().getDescendants(contact.class.Contact).forEach((v) => {
|
||||
const cl = hierarchy.getClass(v)
|
||||
if (hierarchy.hasMixin(cl, view.mixin.ObjectFactory)) {
|
||||
const f = hierarchy.as(cl, view.mixin.ObjectFactory)
|
||||
actions.push({
|
||||
icon: cl.icon as Asset,
|
||||
label: cl.label,
|
||||
action: async () => {
|
||||
closePopup()
|
||||
showPopup(f.component, {}, targetElement)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<Menu actions={actions}/>
|
@ -26,10 +26,6 @@
|
||||
import contact from '../plugin'
|
||||
import Company from './icons/Company.svelte'
|
||||
|
||||
export let space: Ref<Space>
|
||||
|
||||
let _space = space
|
||||
|
||||
export function canClose (): boolean {
|
||||
return object.name === ''
|
||||
}
|
||||
@ -42,7 +38,7 @@
|
||||
const client = getClient()
|
||||
|
||||
async function createOrganization () {
|
||||
await client.createDoc(contact.class.Organization, _space, object)
|
||||
await client.createDoc(contact.class.Organization, contact.space.Contacts, object)
|
||||
|
||||
dispatch('close')
|
||||
}
|
||||
@ -52,10 +48,7 @@
|
||||
label={'Create organization'}
|
||||
okAction={createOrganization}
|
||||
canSave={object.name.length > 0}
|
||||
spaceClass={contact.class.Organizations}
|
||||
spaceLabel={contact.string.OrganizationsFolder}
|
||||
spacePlaceholder={contact.string.SelectFolder}
|
||||
bind:space={_space}
|
||||
space={contact.space.Contacts}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
|
@ -25,10 +25,6 @@
|
||||
import { combineName, Person } from '@anticrm/contact'
|
||||
import contact from '../plugin'
|
||||
|
||||
export let space: Ref<Space>
|
||||
|
||||
let _space = space
|
||||
|
||||
let firstName = ''
|
||||
let lastName = ''
|
||||
|
||||
@ -48,7 +44,7 @@
|
||||
channels: object.channels
|
||||
}
|
||||
|
||||
await client.createDoc(contact.class.Person, _space, person)
|
||||
await client.createDoc(contact.class.Person, contact.space.Contacts, person)
|
||||
|
||||
dispatch('close')
|
||||
}
|
||||
@ -58,10 +54,7 @@
|
||||
label={contact.string.CreatePerson}
|
||||
okAction={createPerson}
|
||||
canSave={firstName.length > 0 && lastName.length > 0}
|
||||
spaceClass={contact.class.Persons}
|
||||
spaceLabel={contact.string.PersonsFolder}
|
||||
spacePlaceholder={contact.string.SelectFolder}
|
||||
bind:space={_space}
|
||||
bind:space={contact.space.Contacts}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
|
@ -25,10 +25,11 @@
|
||||
getClient,
|
||||
KeyedAttribute
|
||||
} from '@anticrm/presentation'
|
||||
import { AnyComponent, Component, getPlatformColorForText, Label } from '@anticrm/ui'
|
||||
import { AnyComponent, Component, Label } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import contact from '../plugin'
|
||||
import { getMixinStyle } from '../utils'
|
||||
|
||||
export let _id: Ref<Contact>
|
||||
let object: Contact
|
||||
@ -77,8 +78,8 @@
|
||||
return keys
|
||||
}
|
||||
|
||||
function getFiltredKeys (objectClass: Ref<Class<Doc>>, ignoreKeys: string[]): KeyedAttribute[] {
|
||||
const keys = [...hierarchy.getAllAttributes(objectClass).entries()]
|
||||
function getFiltredKeys (objectClass: Ref<Class<Doc>>, ignoreKeys: string[], to?: Ref<Class<Doc>>): KeyedAttribute[] {
|
||||
const keys = [...hierarchy.getAllAttributes(objectClass, to).entries()]
|
||||
.filter(([, value]) => value.hidden !== true)
|
||||
.map(([key, attr]) => ({ key, attr }))
|
||||
|
||||
@ -86,7 +87,7 @@
|
||||
}
|
||||
|
||||
function updateKeys (ignoreKeys: string[]): void {
|
||||
const filtredKeys = getFiltredKeys(selectedClass ?? object._class, ignoreKeys)
|
||||
const filtredKeys = getFiltredKeys(selectedClass ?? object._class, ignoreKeys, selectedClass !== objectClass._id ? objectClass._id : undefined)
|
||||
keys = collectionsFilter(filtredKeys, false)
|
||||
collectionKeys = collectionsFilter(filtredKeys, true)
|
||||
}
|
||||
@ -130,14 +131,6 @@
|
||||
|
||||
$: icon = (objectClass?.icon ?? contact.class.Person) as Asset
|
||||
|
||||
function getStyle (id: Ref<Class<Doc>>, selected: boolean): string {
|
||||
const color = getPlatformColorForText(id as string)
|
||||
return `
|
||||
background: ${color + (selected ? 'ff' : '33')};
|
||||
border: 1px solid ${color + (selected ? '0f' : '66')};
|
||||
`
|
||||
}
|
||||
|
||||
let mainEditor: HTMLElement
|
||||
let prevEditor: HTMLElement
|
||||
let maxHeight = 0
|
||||
@ -193,13 +186,13 @@
|
||||
{#if mixins.length > 0}
|
||||
<div class="mixin-container">
|
||||
<div class="mixin-selector"
|
||||
style={getStyle(objectClass._id, selectedClass === objectClass._id)}
|
||||
style={getMixinStyle(objectClass._id, selectedClass === objectClass._id)}
|
||||
on:click={() => { selectedClass = objectClass._id; selectedMixin = undefined }}>
|
||||
<Label label={objectClass.label} />
|
||||
</div>
|
||||
{#each mixins as mixin}
|
||||
<div class="mixin-selector"
|
||||
style={getStyle(mixin._id, selectedClass === mixin._id)}
|
||||
style={getMixinStyle(mixin._id, selectedClass === mixin._id)}
|
||||
on:click={() => { selectedClass = mixin._id; selectedMixin = mixin }}>
|
||||
<Label label={mixin.label} />
|
||||
</div>
|
||||
|
@ -17,11 +17,12 @@
|
||||
import { createEventDispatcher, onMount, afterUpdate } from 'svelte'
|
||||
import { getCurrentAccount, Ref, Space } from '@anticrm/core'
|
||||
import { CircleButton, EditBox, showPopup, IconEdit, IconAdd, Label, IconActivity } from '@anticrm/ui'
|
||||
import { getClient, createQuery, Channels, Avatar } from '@anticrm/presentation'
|
||||
import { getClient, createQuery, Channels, Avatar, AttributeEditor } from '@anticrm/presentation'
|
||||
import setting from '@anticrm/setting'
|
||||
import { IntegrationType } from '@anticrm/setting'
|
||||
import contact from '../plugin'
|
||||
import { combineName, getFirstName, getLastName, Person } from '@anticrm/contact'
|
||||
import Edit from './icons/Edit.svelte'
|
||||
|
||||
export let object: Person
|
||||
|
||||
@ -58,7 +59,7 @@
|
||||
integrations = new Set(res.map((p) => p.type))
|
||||
})
|
||||
|
||||
const sendOpen = () => dispatch('open', { ignoreKeys: ['comments', 'name', 'channels'] })
|
||||
const sendOpen = () => dispatch('open', { ignoreKeys: ['comments', 'name', 'channels', 'city'] })
|
||||
onMount(sendOpen)
|
||||
afterUpdate(sendOpen)
|
||||
</script>
|
||||
@ -76,6 +77,9 @@
|
||||
<div class="name">
|
||||
<EditBox placeholder="Appleseed" maxWidth="20rem" bind:value={lastName} on:change={lastNameChange} />
|
||||
</div>
|
||||
<div class="location">
|
||||
<AttributeEditor maxWidth="20rem" _class={contact.class.Person} {object} key="city" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="separator" />
|
||||
@ -97,7 +101,7 @@
|
||||
<Channels value={object.channels} size={'small'} {integrations} on:click />
|
||||
<div class="ml-1">
|
||||
<CircleButton
|
||||
icon={IconEdit}
|
||||
icon={Edit}
|
||||
size={'small'}
|
||||
selected
|
||||
on:click={(ev) =>
|
||||
@ -132,6 +136,10 @@
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
.location {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 1rem 0;
|
||||
|
@ -0,0 +1,72 @@
|
||||
<!--
|
||||
//
|
||||
// Copyright © 2021 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 { Contact } from '@anticrm/contact'
|
||||
import { ClassifierKind, Doc, Mixin } from '@anticrm/core'
|
||||
import {
|
||||
getClient
|
||||
} from '@anticrm/presentation'
|
||||
import { Label } from '@anticrm/ui'
|
||||
import contact from '../plugin'
|
||||
import { getMixinStyle } from '../utils'
|
||||
|
||||
export let value: Contact
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let mixins: Mixin<Doc>[] = []
|
||||
|
||||
$: if (value !== undefined) {
|
||||
mixins = hierarchy
|
||||
.getDescendants(contact.class.Contact)
|
||||
.filter((m) => hierarchy.getClass(m).kind === ClassifierKind.MIXIN && hierarchy.hasMixin(value, m))
|
||||
.map((m) => hierarchy.getClass(m) as Mixin<Doc>)
|
||||
}
|
||||
</script>
|
||||
{#if mixins.length > 0}
|
||||
<div class="mixin-container">
|
||||
{#each mixins as mixin}
|
||||
<div class="mixin-selector"
|
||||
style={getMixinStyle(mixin._id, true)}>
|
||||
<Label label={mixin.label} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<style lang="scss">
|
||||
.mixin-container {
|
||||
display: flex;
|
||||
.mixin-selector {
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
min-width: 84px;
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 10px;
|
||||
|
||||
text-transform: uppercase;
|
||||
color: #FFFFFF;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
24
plugins/contact-resources/src/components/icons/Edit.svelte
Normal file
24
plugins/contact-resources/src/components/icons/Edit.svelte
Normal file
@ -0,0 +1,24 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
const fill: string = 'var(--theme-caption-color)'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.3,6.5l1.9-1.9c1.1-1.1,1.1-2.8,0-3.8c-1.1-1.1-2.8-1.1-3.8,0L9.5,2.7C10.4,4.3,11.7,5.6,13.3,6.5z M12.3,7.6c-1.6-1-2.9-2.3-3.8-3.8l-6.3,6.3l0,0C1.5,10.7,1.2,11,1,11.4c-0.2,0.4-0.3,0.8-0.5,1.6l-0.4,2c-0.1,0.5-0.1,0.7,0,0.9 c0.1,0.1,0.4,0.1,0.9,0l2-0.4c0.8-0.2,1.3-0.3,1.6-0.5c0.4-0.2,0.7-0.5,1.3-1.1L12.3,7.6z"/>
|
||||
</svg>
|
@ -26,7 +26,9 @@ import EditOrganization from './components/EditOrganization.svelte'
|
||||
import CreatePersons from './components/CreatePersons.svelte'
|
||||
import CreateOrganizations from './components/CreateOrganizations.svelte'
|
||||
import SocialEditor from './components/SocialEditor.svelte'
|
||||
import Contacts from './components/Contacts.svelte'
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import RolePresenter from './components/RolePresenter.svelte'
|
||||
|
||||
export { ContactPresenter, EditContact }
|
||||
|
||||
@ -42,6 +44,8 @@ export default async (): Promise<Resources> => ({
|
||||
EditOrganization,
|
||||
CreatePersons,
|
||||
CreateOrganizations,
|
||||
SocialEditor
|
||||
SocialEditor,
|
||||
Contacts,
|
||||
RolePresenter
|
||||
}
|
||||
})
|
||||
|
@ -32,6 +32,7 @@ export default mergeIds(contactId, contact, {
|
||||
AddSocialLinks: '' as IntlString,
|
||||
Name: '' as IntlString,
|
||||
MakePrivate: '' as IntlString,
|
||||
MakePrivateDescription: '' as IntlString
|
||||
MakePrivateDescription: '' as IntlString,
|
||||
Create: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
10
plugins/contact-resources/src/utils.ts
Normal file
10
plugins/contact-resources/src/utils.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Class, Doc, Ref } from '@anticrm/core'
|
||||
import { getPlatformColorForText } from '@anticrm/ui'
|
||||
|
||||
export function getMixinStyle (id: Ref<Class<Doc>>, selected: boolean): string {
|
||||
const color = getPlatformColorForText(id as string)
|
||||
return `
|
||||
background: ${color + (selected ? 'ff' : '33')};
|
||||
border: 1px solid ${color + (selected ? '0f' : '66')};
|
||||
`
|
||||
}
|
@ -54,19 +54,21 @@ export interface Contact extends Doc {
|
||||
attachments?: number
|
||||
comments?: number
|
||||
channels: Channel[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Person extends Contact {
|
||||
city: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Organization extends Contact {}
|
||||
export interface Person extends Contact {
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Organization extends Contact {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -155,6 +157,7 @@ export default plugin(contactId, {
|
||||
Company: '' as Asset
|
||||
},
|
||||
space: {
|
||||
Employee: '' as Ref<Space>
|
||||
Employee: '' as Ref<Space>,
|
||||
Contacts: '' as Ref<Space>
|
||||
}
|
||||
})
|
||||
|
@ -37,6 +37,7 @@ import { toIntl } from '..'
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Class</th>
|
||||
<th>ObjectID</th>
|
||||
<th>Body</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -44,6 +45,7 @@ import { toIntl } from '..'
|
||||
{#each txes as tx}
|
||||
<tr class='tr-body'>
|
||||
<td>{tx.index}</td>
|
||||
<td>{tx._class}</td>
|
||||
<td>{tx.objectId}</td>
|
||||
<td>{tx.objectClass}</td>
|
||||
<td>
|
||||
|
@ -16,7 +16,7 @@
|
||||
<script lang="ts">
|
||||
import attachment from '@anticrm/attachment'
|
||||
import contact, { combineName, Person } from '@anticrm/contact'
|
||||
import type { Data, MixinData, Ref, Space } from '@anticrm/core'
|
||||
import type { Data, MixinData, Ref } from '@anticrm/core'
|
||||
import { generateId } from '@anticrm/core'
|
||||
import { setPlatformStatus, unknownError } from '@anticrm/platform'
|
||||
import { Avatar, Card, Channels, getClient, PDFViewer } from '@anticrm/presentation'
|
||||
@ -29,10 +29,6 @@
|
||||
import FileUpload from './icons/FileUpload.svelte'
|
||||
import YesNo from './YesNo.svelte'
|
||||
|
||||
export let space: Ref<Space>
|
||||
|
||||
let _space = space
|
||||
|
||||
let firstName = ''
|
||||
let lastName = ''
|
||||
|
||||
@ -66,13 +62,13 @@
|
||||
remote: object.remote
|
||||
}
|
||||
|
||||
const id = await client.createDoc(contact.class.Person, _space, candidate, candidateId)
|
||||
await client.createMixin(id as Ref<Person>, contact.class.Person, _space, recruit.mixin.Candidate, candidateData)
|
||||
const id = await client.createDoc(contact.class.Person, contact.space.Contacts, candidate, candidateId)
|
||||
await client.createMixin(id as Ref<Person>, contact.class.Person, contact.space.Contacts, recruit.mixin.Candidate, candidateData)
|
||||
|
||||
console.log('resume name', resume.name)
|
||||
|
||||
if (resume.uuid !== undefined) {
|
||||
client.addCollection(attachment.class.Attachment, space, id, contact.class.Person, 'attachments', {
|
||||
client.addCollection(attachment.class.Attachment, contact.space.Contacts, id, contact.class.Person, 'attachments', {
|
||||
name: resume.name,
|
||||
file: resume.uuid,
|
||||
size: resume.size,
|
||||
@ -123,10 +119,7 @@
|
||||
<Card label={'Create Candidate'}
|
||||
okAction={createCandidate}
|
||||
canSave={firstName.length > 0 && lastName.length > 0}
|
||||
spaceClass={recruit.class.Candidates}
|
||||
spaceLabel={'Talent Pool'}
|
||||
spacePlaceholder={'Select pool'}
|
||||
bind:space={_space}
|
||||
space={contact.space.Contacts}
|
||||
on:close={() => { dispatch('close') }}>
|
||||
|
||||
<!-- <StatusComponent slot="error" status={{ severity: Severity.ERROR, code: 'Can’t save the object because it already exists' }} /> -->
|
||||
|
@ -1,148 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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 { afterUpdate, createEventDispatcher, onMount } from 'svelte'
|
||||
import { getCurrentAccount, Ref, Space } from '@anticrm/core'
|
||||
import { CircleButton, EditBox, showPopup, IconAdd, Label, IconActivity } from '@anticrm/ui'
|
||||
import { getClient, createQuery, Channels, AttributeEditor, Avatar } from '@anticrm/presentation'
|
||||
import type { Candidate } from '@anticrm/recruit'
|
||||
import Edit from './icons/Edit.svelte'
|
||||
import recruit from '../plugin'
|
||||
import setting from '@anticrm/setting'
|
||||
import { IntegrationType } from '@anticrm/setting'
|
||||
import contact, { combineName, getFirstName, getLastName } from '@anticrm/contact'
|
||||
|
||||
export let object: Candidate
|
||||
|
||||
let firstName = getFirstName(object.name)
|
||||
let lastName = getLastName(object.name)
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function saveChannels (result: any) {
|
||||
if (result !== undefined) {
|
||||
object.channels = result
|
||||
client.updateDoc(object._class, object.space, object._id, { channels: result })
|
||||
}
|
||||
}
|
||||
|
||||
function firstNameChange () {
|
||||
client.updateDoc(object._class, object.space, object._id, {
|
||||
name: combineName(firstName, getLastName(object.name))
|
||||
})
|
||||
}
|
||||
|
||||
function lastNameChange () {
|
||||
client.updateDoc(object._class, object.space, object._id, {
|
||||
name: combineName(getFirstName(object.name), lastName)
|
||||
})
|
||||
}
|
||||
|
||||
const accountId = getCurrentAccount()._id
|
||||
let integrations: Set<Ref<IntegrationType>> = new Set<Ref<IntegrationType>>()
|
||||
const settingsQuery = createQuery()
|
||||
$: settingsQuery.query(setting.class.Integration, { space: accountId as string as Ref<Space> }, (res) => {
|
||||
integrations = new Set(res.map((p) => p.type))
|
||||
})
|
||||
|
||||
const sendOpen = () => dispatch('open', { ignoreKeys: ['comments', 'name', 'channels', 'title'] })
|
||||
onMount(sendOpen)
|
||||
afterUpdate(sendOpen)
|
||||
</script>
|
||||
|
||||
{#if object !== undefined}
|
||||
<div class="flex-row-streach flex-grow">
|
||||
<div class="mr-8">
|
||||
<Avatar avatar={object.avatar} size={'x-large'} />
|
||||
</div>
|
||||
<div class="flex-grow flex-col">
|
||||
<div class="flex-grow flex-col">
|
||||
<div class="name">
|
||||
<EditBox placeholder="John" maxWidth="20rem" bind:value={firstName} on:change={firstNameChange} />
|
||||
</div>
|
||||
<div class="name">
|
||||
<EditBox placeholder="Appleseed" maxWidth="20rem" bind:value={lastName} on:change={lastNameChange} />
|
||||
</div>
|
||||
<div class="title">
|
||||
<AttributeEditor maxWidth="20rem" _class={recruit.mixin.Candidate} {object} key="title" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="separator" />
|
||||
|
||||
<div class="flex-between channels">
|
||||
<div class="flex-row-center">
|
||||
{#if !object.channels || object.channels.length === 0}
|
||||
<CircleButton
|
||||
icon={IconAdd}
|
||||
size={'small'}
|
||||
selected
|
||||
on:click={(ev) =>
|
||||
showPopup(contact.component.SocialEditor, { values: object.channels ?? [] }, ev.target, (result) => {
|
||||
saveChannels(result)
|
||||
})}
|
||||
/>
|
||||
<span class="ml-2"><Label label={'Add social links'} /></span>
|
||||
{:else}
|
||||
<Channels value={object.channels} {integrations} size={'small'} on:click />
|
||||
<div class="ml-1">
|
||||
<CircleButton
|
||||
icon={Edit}
|
||||
size={'small'}
|
||||
on:click={(ev) =>
|
||||
showPopup(contact.component.SocialEditor, { values: object.channels ?? [] }, ev.target, (result) => {
|
||||
saveChannels(result)
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex-row-center">
|
||||
<a href={'#'} class="flex-row-center" on:click>
|
||||
<CircleButton icon={IconActivity} size={'small'} primary on:click />
|
||||
<span class="ml-2 small-text">View activity</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.name {
|
||||
font-weight: 500;
|
||||
font-size: 1.25rem;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
.title {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.channels {
|
||||
margin-top: 0.75rem;
|
||||
span {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
.separator {
|
||||
margin: 1rem 0;
|
||||
height: 1px;
|
||||
background-color: var(--theme-card-divider);
|
||||
}
|
||||
</style>
|
@ -15,12 +15,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Avatar } from '@anticrm/presentation'
|
||||
import { showPopup, Label, ActionIcon, IconMoreH } from '@anticrm/ui'
|
||||
import { showPopup, ActionIcon, IconMoreH } from '@anticrm/ui'
|
||||
import type { WithLookup } from '@anticrm/core'
|
||||
import type { Applicant } from '@anticrm/recruit'
|
||||
|
||||
import EditCandidate from './EditCandidate.svelte'
|
||||
|
||||
import { CommentsPresenter } from '@anticrm/chunter-resources'
|
||||
import { AttachmentsPresenter } from '@anticrm/attachment-resources'
|
||||
import { formatName } from '@anticrm/contact'
|
||||
@ -30,7 +28,7 @@
|
||||
export let object: WithLookup<Applicant>
|
||||
export let draggable: boolean
|
||||
|
||||
function showCandidate() {
|
||||
function showCandidate () {
|
||||
showPopup(EditContact, { _id: object.attachedTo }, 'full')
|
||||
}
|
||||
</script>
|
||||
|
@ -14,22 +14,20 @@
|
||||
//
|
||||
|
||||
import type { Client, Doc } from '@anticrm/core'
|
||||
|
||||
import CreateVacancy from './components/CreateVacancy.svelte'
|
||||
import CreateApplication from './components/CreateApplication.svelte'
|
||||
import EditCandidate from './components/EditCandidate.svelte'
|
||||
import KanbanCard from './components/KanbanCard.svelte'
|
||||
import EditVacancy from './components/EditVacancy.svelte'
|
||||
import ApplicationPresenter from './components/ApplicationPresenter.svelte'
|
||||
import ApplicationsPresenter from './components/ApplicationsPresenter.svelte'
|
||||
import TemplatesIcon from './components/TemplatesIcon.svelte'
|
||||
import Applications from './components/Applications.svelte'
|
||||
import EditApplication from './components/EditApplication.svelte'
|
||||
import Candidates from './components/Candidates.svelte'
|
||||
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import { OK, Resources, Severity, Status } from '@anticrm/platform'
|
||||
import { Applicant } from '@anticrm/recruit'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import ApplicationPresenter from './components/ApplicationPresenter.svelte'
|
||||
import Applications from './components/Applications.svelte'
|
||||
import ApplicationsPresenter from './components/ApplicationsPresenter.svelte'
|
||||
import Candidates from './components/Candidates.svelte'
|
||||
import CreateApplication from './components/CreateApplication.svelte'
|
||||
import CreateCandidate from './components/CreateCandidate.svelte'
|
||||
import CreateVacancy from './components/CreateVacancy.svelte'
|
||||
import EditApplication from './components/EditApplication.svelte'
|
||||
import EditVacancy from './components/EditVacancy.svelte'
|
||||
import KanbanCard from './components/KanbanCard.svelte'
|
||||
import TemplatesIcon from './components/TemplatesIcon.svelte'
|
||||
import recruit from './plugin'
|
||||
|
||||
async function createApplication (object: Doc): Promise<void> {
|
||||
@ -63,7 +61,6 @@ export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
CreateVacancy,
|
||||
CreateApplication,
|
||||
EditCandidate,
|
||||
EditApplication,
|
||||
KanbanCard,
|
||||
ApplicationPresenter,
|
||||
@ -71,6 +68,7 @@ export default async (): Promise<Resources> => ({
|
||||
EditVacancy,
|
||||
TemplatesIcon,
|
||||
Applications,
|
||||
Candidates
|
||||
Candidates,
|
||||
CreateCandidate
|
||||
}
|
||||
})
|
||||
|
@ -14,20 +14,16 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Doc, Class, Ref } from '@anticrm/core'
|
||||
import type { Asset, IntlString, Resource } from '@anticrm/platform'
|
||||
import type { Class, Doc, Ref } from '@anticrm/core'
|
||||
import type { Asset, Resource } from '@anticrm/platform'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Menu } from '@anticrm/ui'
|
||||
import { Action, Menu } from '@anticrm/ui'
|
||||
import { getActions } from '../utils'
|
||||
|
||||
export let object: Doc
|
||||
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
|
||||
export let actions: {
|
||||
label: IntlString
|
||||
icon: Asset
|
||||
action: () => void
|
||||
}[] = []
|
||||
export let actions: Action[] = []
|
||||
|
||||
const client = getClient()
|
||||
|
||||
@ -39,8 +35,8 @@
|
||||
getActions(client, object, baseMenuClass).then(result => {
|
||||
actions = result.map(a => ({
|
||||
label: a.label,
|
||||
icon: a.icon,
|
||||
action: () => { invokeAction(a.action) }
|
||||
icon: a.icon as Asset,
|
||||
action: async () => { invokeAction(a.action) }
|
||||
}))
|
||||
})
|
||||
|
||||
|
@ -121,6 +121,16 @@ export interface BuildModelOptions {
|
||||
ignoreMissing?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Define document create popup widget
|
||||
*
|
||||
* @public
|
||||
*
|
||||
*/
|
||||
export interface ObjectFactory extends Class<Obj> {
|
||||
component: AnyComponent
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -129,7 +139,8 @@ const view = plugin(viewId, {
|
||||
AttributeEditor: '' as Ref<Mixin<AttributeEditor>>,
|
||||
AttributePresenter: '' as Ref<Mixin<AttributePresenter>>,
|
||||
ObjectEditor: '' as Ref<Mixin<ObjectEditor>>,
|
||||
ObjectValidator: '' as Ref<Mixin<ObjectValidator>>
|
||||
ObjectValidator: '' as Ref<Mixin<ObjectValidator>>,
|
||||
ObjectFactory: '' as Ref<Mixin<ObjectFactory>>
|
||||
},
|
||||
class: {
|
||||
ViewletDescriptor: '' as Ref<Class<ViewletDescriptor>>,
|
||||
|
@ -12,28 +12,32 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import ActivityStatus from './ActivityStatus.svelte'
|
||||
import Applications from './Applications.svelte'
|
||||
import NavHeader from './NavHeader.svelte'
|
||||
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
import type { Ref, Space, Client } from '@anticrm/core'
|
||||
import type { Client, Ref, Space } from '@anticrm/core'
|
||||
import core from '@anticrm/core'
|
||||
import { Avatar, createQuery, setClient } from '@anticrm/presentation'
|
||||
import {
|
||||
AnyComponent,
|
||||
AnySvelteComponent,
|
||||
closeTooltip,
|
||||
Component,
|
||||
location,
|
||||
Popup,
|
||||
showPopup,
|
||||
TooltipInstance
|
||||
} from '@anticrm/ui'
|
||||
import type { Application, NavigatorModel, ViewConfiguration } from '@anticrm/workbench'
|
||||
import { setClient, Avatar, createQuery } from '@anticrm/presentation'
|
||||
import { onDestroy } from 'svelte'
|
||||
import workbench from '../plugin'
|
||||
|
||||
import AccountPopup from './AccountPopup.svelte'
|
||||
import ActivityStatus from './ActivityStatus.svelte'
|
||||
import AppItem from './AppItem.svelte'
|
||||
import Applications from './Applications.svelte'
|
||||
import Archive from './Archive.svelte'
|
||||
import TopMenu from './icons/TopMenu.svelte'
|
||||
import NavHeader from './NavHeader.svelte'
|
||||
import Navigator from './Navigator.svelte'
|
||||
import SpaceView from './SpaceView.svelte'
|
||||
|
||||
import { AnyComponent, Component, location, Popup, showPopup, TooltipInstance, closeTooltip, ActionIcon, IconEdit, AnySvelteComponent } from '@anticrm/ui'
|
||||
import core from '@anticrm/core'
|
||||
import AccountPopup from './AccountPopup.svelte'
|
||||
import AppItem from './AppItem.svelte'
|
||||
import TopMenu from './icons/TopMenu.svelte'
|
||||
import Archive from './Archive.svelte'
|
||||
|
||||
export let client: Client
|
||||
|
||||
@ -48,35 +52,37 @@
|
||||
let createItemDialog: AnyComponent | undefined
|
||||
let navigatorModel: NavigatorModel | undefined
|
||||
|
||||
onDestroy(location.subscribe(async (loc) => {
|
||||
currentApp = loc.path[1] as Ref<Application>
|
||||
currentApplication = (await client.findAll(workbench.class.Application, { _id: currentApp }))[0]
|
||||
navigatorModel = currentApplication?.navigatorModel
|
||||
let currentFolder = loc.path[2] as Ref<Space>
|
||||
ownSpecialComponent = getOwnSpecialComponent(currentFolder)
|
||||
onDestroy(
|
||||
location.subscribe(async (loc) => {
|
||||
currentApp = loc.path[1] as Ref<Application>
|
||||
currentApplication = (await client.findAll(workbench.class.Application, { _id: currentApp }))[0]
|
||||
navigatorModel = currentApplication?.navigatorModel
|
||||
const currentFolder = loc.path[2] as Ref<Space>
|
||||
ownSpecialComponent = getOwnSpecialComponent(currentFolder)
|
||||
|
||||
if (ownSpecialComponent !== undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
specialComponent = getSpecialComponent(currentFolder)
|
||||
if (ownSpecialComponent !== undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (specialComponent !== undefined) {
|
||||
return
|
||||
}
|
||||
specialComponent = getSpecialComponent(currentFolder)
|
||||
|
||||
const space = (await client.findAll(core.class.Space, { _id: currentFolder }))[0]
|
||||
currentSpace = currentFolder
|
||||
if (space) {
|
||||
const spaceClass = client.getHierarchy().getClass(space._class) // (await client.findAll(core.class.Class, { _id: space._class }))[0]
|
||||
const view = client.getHierarchy().as(spaceClass, workbench.mixin.SpaceView)
|
||||
currentView = view.view
|
||||
createItemDialog = currentView.createItemDialog
|
||||
} else {
|
||||
currentView = undefined
|
||||
createItemDialog = undefined
|
||||
}
|
||||
}))
|
||||
if (specialComponent !== undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const space = (await client.findAll(core.class.Space, { _id: currentFolder }))[0]
|
||||
currentSpace = currentFolder
|
||||
if (space) {
|
||||
const spaceClass = client.getHierarchy().getClass(space._class) // (await client.findAll(core.class.Class, { _id: space._class }))[0]
|
||||
const view = client.getHierarchy().as(spaceClass, workbench.mixin.SpaceView)
|
||||
currentView = view.view
|
||||
createItemDialog = currentView.createItemDialog
|
||||
} else {
|
||||
currentView = undefined
|
||||
createItemDialog = undefined
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
function getOwnSpecialComponent (id: string): AnySvelteComponent | undefined {
|
||||
if (id === 'archive') {
|
||||
@ -85,14 +91,16 @@
|
||||
}
|
||||
|
||||
function getSpecialComponent (id: string): AnyComponent | undefined {
|
||||
let special = navigatorModel?.specials?.find((x) => x.id === id)
|
||||
const special = navigatorModel?.specials?.find((x) => x.id === id)
|
||||
return special?.component
|
||||
}
|
||||
|
||||
let apps: Application[] = []
|
||||
|
||||
const query = createQuery()
|
||||
$: query.query(workbench.class.Application, { hidden: false }, result => { apps = result })
|
||||
$: query.query(workbench.class.Application, { hidden: false }, (result) => {
|
||||
apps = result
|
||||
})
|
||||
|
||||
let visibileNav: boolean = true
|
||||
const toggleNav = async () => {
|
||||
@ -104,16 +112,18 @@
|
||||
{#if client}
|
||||
<svg class="svg-mask">
|
||||
<clipPath id="notify-normal">
|
||||
<path d="M0,0v52.5h52.5V0H0z M34,23.2c-3.2,0-5.8-2.6-5.8-5.8c0-3.2,2.6-5.8,5.8-5.8c3.2,0,5.8,2.6,5.8,5.8 C39.8,20.7,37.2,23.2,34,23.2z"/>
|
||||
<path
|
||||
d="M0,0v52.5h52.5V0H0z M34,23.2c-3.2,0-5.8-2.6-5.8-5.8c0-3.2,2.6-5.8,5.8-5.8c3.2,0,5.8,2.6,5.8,5.8 C39.8,20.7,37.2,23.2,34,23.2z"
|
||||
/>
|
||||
</clipPath>
|
||||
<clipPath id="notify-small">
|
||||
<path d="M0,0v45h45V0H0z M29.5,20c-2.8,0-5-2.2-5-5s2.2-5,5-5s5,2.2,5,5S32.3,20,29.5,20z"/>
|
||||
<path d="M0,0v45h45V0H0z M29.5,20c-2.8,0-5-2.2-5-5s2.2-5,5-5s5,2.2,5,5S32.3,20,29.5,20z" />
|
||||
</clipPath>
|
||||
</svg>
|
||||
<div class="container">
|
||||
<div class="panel-app" on:click={toggleNav}>
|
||||
<div class="flex-col">
|
||||
<ActivityStatus status="active"/>
|
||||
<ActivityStatus status="active" />
|
||||
<AppItem
|
||||
icon={TopMenu}
|
||||
label={visibileNav ? workbench.string.HideMenu : workbench.string.ShowMenu}
|
||||
@ -121,28 +131,37 @@
|
||||
action={toggleNav}
|
||||
/>
|
||||
</div>
|
||||
<Applications {apps} active={currentApp}/>
|
||||
<Applications {apps} active={currentApp} />
|
||||
<div class="flex-center" style="min-height: 6.25rem;">
|
||||
<div class="cursor-pointer" on:click|stopPropagation={(el) => { showPopup(AccountPopup, { }, 'account') }}>
|
||||
<div
|
||||
class="cursor-pointer"
|
||||
on:click|stopPropagation={(el) => {
|
||||
showPopup(AccountPopup, {}, 'account')
|
||||
}}
|
||||
>
|
||||
<Avatar size={'medium'} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if navigator && visibileNav}
|
||||
<div class="panel-navigator">
|
||||
{#if currentApplication}
|
||||
<NavHeader label={currentApplication.label} />
|
||||
{/if}
|
||||
<Navigator model={navigatorModel} />
|
||||
</div>
|
||||
{#if currentApplication && navigatorModel && navigator && visibileNav}
|
||||
<div class="panel-navigator">
|
||||
{#if currentApplication}
|
||||
<NavHeader label={currentApplication.label} />
|
||||
{/if}
|
||||
<Navigator model={navigatorModel} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="panel-component">
|
||||
{#if currentApplication && currentApplication.component}
|
||||
<Component is={currentApplication.component} />
|
||||
{/if}
|
||||
|
||||
{#if ownSpecialComponent}
|
||||
<svelte:component this={ownSpecialComponent} model={navigatorModel} />
|
||||
{:else if specialComponent}
|
||||
<Component is={specialComponent} />
|
||||
{:else}
|
||||
<SpaceView {currentSpace} {currentView} {createItemDialog}/>
|
||||
<SpaceView {currentSpace} {currentView} {createItemDialog} />
|
||||
{/if}
|
||||
</div>
|
||||
<!-- <div class="aside"><Chat thread/></div> -->
|
||||
|
@ -28,6 +28,9 @@ export interface Application extends Doc {
|
||||
icon: Asset
|
||||
hidden: boolean
|
||||
navigatorModel?: NavigatorModel
|
||||
|
||||
// Component will be displayed in case navigator model is not defined, or nothing is selected in navigator model
|
||||
component?: AnyComponent
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user