Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-01-18 16:21:32 +06:00 committed by GitHub
parent 3d21b2de1e
commit b56f3bba7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 576 additions and 650 deletions

View File

@ -19,7 +19,6 @@ import type { Ref, Doc, Class, Domain } from '@anticrm/core'
import { IndexKind } from '@anticrm/core'
import core, { TSpace, TDoc, TAttachedDoc } from '@anticrm/model-core'
import type { Backlink, Channel, Message, Comment } from '@anticrm/chunter'
import type { AnyComponent } from '@anticrm/ui'
import activity from '@anticrm/activity'
import workbench from '@anticrm/model-workbench'
@ -72,7 +71,6 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: chunter.class.Message,
descriptor: chunter.viewlet.Chat,
open: 'X' as AnyComponent,
config: {}
})

View File

@ -78,8 +78,7 @@ export class TContact extends TDoc implements Contact {
@Model(contact.class.Person, contact.class.Contact)
@UX('Person' as IntlString, contact.icon.Person, undefined, 'name')
export class TPerson extends TContact implements Person {
}
export class TPerson extends TContact implements Person {}
@Model(contact.class.Organization, contact.class.Contact)
@UX('Organization' as IntlString, contact.icon.Company, undefined, 'name')
@ -124,17 +123,21 @@ export function createModel (builder: Builder): void {
component: contact.component.CreateOrganization
})
builder.createDoc(workbench.class.Application, core.space.Model, {
label: contact.string.Contacts,
icon: contact.icon.Person,
hidden: false,
component: contact.component.Contacts
}, contact.app.Contacts)
builder.createDoc(
workbench.class.Application,
core.space.Model,
{
label: contact.string.Contacts,
icon: contact.icon.Person,
hidden: false,
component: contact.component.Contacts
},
contact.app.Contacts
)
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: contact.class.Contact,
descriptor: view.viewlet.Table,
open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {},
config: [
@ -142,7 +145,7 @@ export function createModel (builder: Builder): void {
'city',
{ presenter: attachment.component.AttachmentsPresenter, label: 'Files', sortingKey: 'attachments' },
'modifiedOn',
{ presenter: contact.component.RolePresenter, label: 'Role' },
{ presenter: view.component.RolePresenter, label: 'Role' },
'channels'
]
})

View File

@ -31,14 +31,12 @@ export const ids = mergeIds(contactId, contact, {
ChannelsPresenter: '' as AnyComponent,
CreatePerson: '' as AnyComponent,
EditPerson: '' as AnyComponent,
EditContact: '' as AnyComponent,
EditOrganization: '' as AnyComponent,
CreateOrganization: '' as AnyComponent,
CreatePersons: '' as AnyComponent,
CreateOrganizations: '' as AnyComponent,
OrganizationPresenter: '' as AnyComponent,
Contacts: '' as AnyComponent,
RolePresenter: '' as AnyComponent
Contacts: '' as AnyComponent
},
string: {
Organizations: '' as IntlString,

View File

@ -87,10 +87,13 @@ export function createModel (builder: Builder): void {
editor: inventory.component.Variants
})
builder.mixin(inventory.class.Product, core.class.Class, view.mixin.ObjectEditor, {
editor: inventory.component.EditProduct
})
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: inventory.class.Product,
descriptor: view.viewlet.Table,
open: inventory.component.EditProduct,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {

View File

@ -27,7 +27,7 @@ import task, { TSpaceWithStates, TTask } from '@anticrm/model-task'
import view from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import type { IntlString } from '@anticrm/platform'
import type { } from '@anticrm/view'
import type {} from '@anticrm/view'
import lead from './plugin'
@Model(lead.class.Funnel, task.class.SpaceWithStates)
@ -118,7 +118,6 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: lead.class.Lead,
descriptor: view.viewlet.Table,
open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {
@ -140,7 +139,6 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: lead.class.Lead,
descriptor: task.viewlet.Kanban,
open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {

View File

@ -150,7 +150,6 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: recruit.mixin.Candidate,
descriptor: view.viewlet.Table,
open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
// lookup: {
@ -172,7 +171,6 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: recruit.class.Applicant,
descriptor: view.viewlet.Table,
open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {
@ -198,7 +196,6 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: recruit.class.Applicant,
descriptor: task.viewlet.Kanban,
open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {
@ -212,7 +209,6 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: recruit.class.Applicant,
descriptor: task.viewlet.StatusTable,
open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {

View File

@ -20,7 +20,20 @@ import attachment from '@anticrm/model-attachment'
import type { Employee } from '@anticrm/contact'
import contact from '@anticrm/contact'
import { Arr, Class, Doc, Domain, DOMAIN_MODEL, FindOptions, Ref, Space, Timestamp } from '@anticrm/core'
import { Builder, Collection, Implements, Mixin, Model, Prop, Hidden, TypeBoolean, TypeDate, TypeRef, TypeString, UX } from '@anticrm/model'
import {
Builder,
Collection,
Implements,
Mixin,
Model,
Prop,
Hidden,
TypeBoolean,
TypeDate,
TypeRef,
TypeString,
UX
} from '@anticrm/model'
import chunter from '@anticrm/model-chunter'
import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@anticrm/model-core'
import view from '@anticrm/model-view'
@ -281,7 +294,6 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: task.class.Issue,
descriptor: view.viewlet.Table,
open: task.component.EditTask,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {
@ -306,10 +318,13 @@ export function createModel (builder: Builder): void {
editor: task.component.EditIssue
})
builder.mixin(task.class.Task, core.class.Class, view.mixin.ObjectEditorHeader, {
editor: task.component.TaskHeader
})
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: task.class.Issue,
descriptor: task.viewlet.Kanban,
open: task.component.EditTask,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {

View File

@ -46,7 +46,6 @@ export default mergeIds(taskId, task, {
ProjectView: '' as AnyComponent,
CreateProject: '' as AnyComponent,
CreateTask: '' as AnyComponent,
EditTask: '' as AnyComponent,
EditIssue: '' as AnyComponent,
TaskPresenter: '' as AnyComponent,
KanbanCard: '' as AnyComponent,
@ -57,7 +56,8 @@ export default mergeIds(taskId, task, {
KanbanView: '' as AnyComponent,
Todos: '' as AnyComponent,
TodoItemPresenter: '' as AnyComponent,
StatusTableView: '' as AnyComponent
StatusTableView: '' as AnyComponent,
TaskHeader: '' as AnyComponent
},
string: {
Task: '' as IntlString,

View File

@ -19,7 +19,18 @@ 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, ObjectFactory, ObjectValidator, Viewlet, ViewletDescriptor } from '@anticrm/view'
import type {
Action,
ActionTarget,
AttributeEditor,
AttributePresenter,
ObjectEditor,
ObjectEditorHeader,
ObjectFactory,
ObjectValidator,
Viewlet,
ViewletDescriptor
} from '@anticrm/view'
import view from './plugin'
@Mixin(view.mixin.AttributeEditor, core.class.Class)
@ -37,9 +48,14 @@ export class TObjectEditor extends TClass implements ObjectEditor {
editor!: AnyComponent
}
@Mixin(view.mixin.ObjectEditorHeader, core.class.Class)
export class TObjectEditorHeader extends TClass implements ObjectEditorHeader {
editor!: AnyComponent
}
@Mixin(view.mixin.ObjectValidator, core.class.Class)
export class TObjectValidator extends TClass implements ObjectValidator {
validator!: Resource<(<T extends Doc>(doc: T, client: Client) => Promise<Status<{}>>)>
validator!: Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status<{}>>>
}
@Mixin(view.mixin.ObjectFactory, core.class.Class)
@ -75,7 +91,18 @@ export class TActionTarget extends TDoc implements ActionTarget {
}
export function createModel (builder: Builder): void {
builder.createModel(TAttributeEditor, TAttributePresenter, TObjectEditor, TViewletDescriptor, TViewlet, TAction, TActionTarget, TObjectValidator, TObjectFactory)
builder.createModel(
TAttributeEditor,
TAttributePresenter,
TObjectEditor,
TViewletDescriptor,
TViewlet,
TAction,
TActionTarget,
TObjectValidator,
TObjectFactory,
TObjectEditorHeader
)
builder.mixin(core.class.TypeString, core.class.Class, view.mixin.AttributeEditor, {
editor: view.component.StringEditor
@ -105,28 +132,43 @@ export function createModel (builder: Builder): void {
editor: view.component.DateEditor
})
builder.createDoc(view.class.ViewletDescriptor, core.space.Model, {
label: 'Table' as IntlString,
icon: view.icon.Table,
component: view.component.TableView
}, view.viewlet.Table)
builder.createDoc(
view.class.ViewletDescriptor,
core.space.Model,
{
label: 'Table' as IntlString,
icon: view.icon.Table,
component: view.component.TableView
},
view.viewlet.Table
)
builder.createDoc(view.class.Action, core.space.Model, {
label: 'Delete' as IntlString,
icon: view.icon.Delete,
action: view.actionImpl.Delete
}, view.action.Delete)
builder.createDoc(
view.class.Action,
core.space.Model,
{
label: 'Delete' as IntlString,
icon: view.icon.Delete,
action: view.actionImpl.Delete
},
view.action.Delete
)
builder.createDoc(view.class.ActionTarget, core.space.Model, {
target: core.class.Doc,
action: view.action.Delete
})
builder.createDoc(view.class.Action, core.space.Model, {
label: 'Move' as IntlString,
icon: view.icon.Move,
action: view.actionImpl.Move
}, view.action.Move)
builder.createDoc(
view.class.Action,
core.space.Model,
{
label: 'Move' as IntlString,
icon: view.icon.Move,
action: view.actionImpl.Move
},
view.action.Move
)
builder.createDoc(view.class.ActionTarget, core.space.Model, {
target: core.class.Doc,

View File

@ -36,6 +36,7 @@ export default mergeIds(viewId, view, {
TimestampPresenter: '' as AnyComponent,
DateEditor: '' as AnyComponent,
DatePresenter: '' as AnyComponent,
TableView: '' as AnyComponent
TableView: '' as AnyComponent,
RolePresenter: '' as AnyComponent
}
})

View File

@ -1,247 +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 { Contact, formatName } from '@anticrm/contact'
import core, { Class, ClassifierKind, Doc, Mixin, Ref } from '@anticrm/core'
import { Panel } from '@anticrm/panel'
import { Asset } from '@anticrm/platform'
import {
AttributesBar,
createQuery,
getAttributePresenterClass,
getClient,
KeyedAttribute
} from '@anticrm/presentation'
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
let objectClass: Class<Doc>
let rightSection: AnyComponent | undefined
let fullSize: boolean = true
const client = getClient()
const hierarchy = client.getHierarchy()
const docKeys: Set<string> = new Set<string>(hierarchy.getAllAttributes(core.class.AttachedDoc).keys())
const query = createQuery()
$: _id &&
query.query(contact.class.Contact, { _id }, (result) => {
object = result[0]
})
$: if (object) objectClass = client.getHierarchy().getClass(object._class)
let selectedClass: Ref<Class<Doc>> | undefined
let prevSelected = selectedClass
let keys: KeyedAttribute[] = []
let collectionKeys: KeyedAttribute[] = []
let mixins: Mixin<Doc>[] = []
let selectedMixin: Mixin<Doc> | undefined
$: if (object && prevSelected !== object._class) {
prevSelected = object._class
selectedClass = objectClass._id
selectedMixin = undefined
const h = client.getHierarchy()
mixins = h
.getDescendants(contact.class.Contact)
.filter((m) => h.getClass(m).kind === ClassifierKind.MIXIN && h.hasMixin(object, m))
.map((m) => h.getClass(m) as Mixin<Doc>)
}
const dispatch = createEventDispatcher()
function filterKeys (keys: KeyedAttribute[], ignoreKeys: string[]): KeyedAttribute[] {
keys = keys.filter((k) => !docKeys.has(k.key))
keys = keys.filter((k) => !ignoreKeys.includes(k.key))
return keys
}
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 }))
return filterKeys(keys, ignoreKeys)
}
function updateKeys (ignoreKeys: string[]): void {
const filtredKeys = getFiltredKeys(selectedClass ?? object._class, ignoreKeys, selectedClass !== objectClass._id ? objectClass._id : undefined)
keys = collectionsFilter(filtredKeys, false)
collectionKeys = collectionsFilter(filtredKeys, true)
}
function collectionsFilter (keys: KeyedAttribute[], get: boolean): KeyedAttribute[] {
const result: KeyedAttribute[] = []
for (const key of keys) {
if (isCollectionAttr(key) === get) result.push(key)
}
return result
}
function isCollectionAttr (key: KeyedAttribute): boolean {
return hierarchy.isDerived(key.attr.type._class, core.class.Collection)
}
async function getEditor (_class: Ref<Class<Doc>>): Promise<AnyComponent> {
const clazz = hierarchy.getClass(_class)
const editorMixin = hierarchy.as(clazz, view.mixin.ObjectEditor)
if (editorMixin?.editor == null && clazz.extends != null) return getEditor(clazz.extends)
return editorMixin.editor
}
async function getEditorOrDefault (
_class: Ref<Class<Doc>> | undefined,
defaultClass: Ref<Class<Doc>>
): Promise<AnyComponent> {
const editor = _class !== undefined ? await getEditor(_class) : undefined
if (editor !== undefined) {
return editor
}
return getEditor(defaultClass)
}
async function getCollectionEditor (key: KeyedAttribute): Promise<AnyComponent> {
const attrClass = getAttributePresenterClass(key.attr)
const clazz = client.getHierarchy().getClass(attrClass)
const editorMixin = client.getHierarchy().as(clazz, view.mixin.AttributeEditor)
return editorMixin.editor
}
$: icon = (objectClass?.icon ?? contact.class.Person) as Asset
let mainEditor: HTMLElement
let prevEditor: HTMLElement
let maxHeight = 0
const observer = new ResizeObserver(() => {
const curHeight = mainEditor.clientHeight
maxHeight = Math.max(maxHeight, curHeight)
})
$: if (mainEditor != null) {
if (prevEditor != null) {
observer.unobserve(prevEditor)
}
prevEditor = mainEditor
observer.observe(mainEditor)
}
onDestroy(() => {
observer.disconnect()
})
function getCollectionCounter (object: Doc, key: KeyedAttribute): number {
if (client.getHierarchy().isMixin(key.attr.attributeOf)) {
return (client.getHierarchy().as(object, key.attr.attributeOf) as any)[key.key]
}
return (object as any)[key.key] ?? 0
}
</script>
{#if object !== undefined}
<Panel
{icon}
title={formatName(object.name)}
{rightSection}
{fullSize}
{object}
on:close={() => {
dispatch('close')
}}
>
<div slot="subtitle">
{#if keys}
<AttributesBar {object} {keys} />
{/if}
</div>
<div class='main-editor' bind:this={mainEditor} style={`min-height: ${maxHeight}px;`}>
{#await getEditorOrDefault(selectedClass, object._class) then is}
<Component
{is}
props={{ object }}
on:open={(ev) => {
updateKeys(ev.detail.ignoreKeys)
}}
on:click={(ev) => {
fullSize = true
rightSection = ev.detail.presenter
}}
/>
{/await}
</div>
{#if mixins.length > 0}
<div class="mixin-container">
<div class="mixin-selector"
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={getMixinStyle(mixin._id, selectedClass === mixin._id)}
on:click={() => { selectedClass = mixin._id; selectedMixin = mixin }}>
<Label label={mixin.label} />
</div>
{/each}
</div>
{/if}
{#each collectionKeys as collection}
<div class="mt-14">
{#await getCollectionEditor(collection) then is}
<Component {is} props={{ objectId: object._id, _class: object._class, space: object.space, [collection.key]: getCollectionCounter(object, collection) }} />
{/await}
</div>
{/each}
</Panel>
{/if}
<style lang="scss">
.main-editor {
display: flex;
justify-content: center;
flex-direction: column;
}
.mixin-container {
margin-top: 2rem;
display: flex;
.mixin-selector {
margin-left: .5rem;
cursor: pointer;
height: 1.5rem;
min-width: 5.25rem;
border-radius: .5rem;
font-weight: 500;
font-size: .625rem;
text-transform: uppercase;
color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@ -14,15 +14,15 @@
// limitations under the License.
-->
<script lang="ts">
import { Person } from '@anticrm/contact'
import { Organization } from '@anticrm/contact'
import { showPopup } from '@anticrm/ui'
import Company from './icons/Company.svelte'
import EditContact from './EditContact.svelte'
import { EditDoc } from '@anticrm/view-resources'
export let value: Person
export let value: Organization
async function onClick () {
showPopup(EditContact, { _id: value._id }, 'full')
showPopup(EditDoc, { _id: value._id, _class: value._class }, 'full')
}
</script>

View File

@ -17,12 +17,12 @@
import { formatName, Person } from '@anticrm/contact'
import { Avatar } from '@anticrm/presentation'
import { showPopup } from '@anticrm/ui'
import EditContact from './EditContact.svelte'
import { EditDoc } from '@anticrm/view-resources'
export let value: Person
async function onClick () {
showPopup(EditContact, { _id: value._id }, 'full')
showPopup(EditDoc, { _id: value._id, _class: value._class }, 'full')
}
</script>

View File

@ -20,7 +20,6 @@ import OrganizationPresenter from './components/OrganizationPresenter.svelte'
import ChannelsPresenter from './components/ChannelsPresenter.svelte'
import CreatePerson from './components/CreatePerson.svelte'
import CreateOrganization from './components/CreateOrganization.svelte'
import EditContact from './components/EditContact.svelte'
import EditPerson from './components/EditPerson.svelte'
import EditOrganization from './components/EditOrganization.svelte'
import CreatePersons from './components/CreatePersons.svelte'
@ -28,9 +27,8 @@ 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 }
export { ContactPresenter }
export default async (): Promise<Resources> => ({
component: {
@ -45,7 +43,6 @@ export default async (): Promise<Resources> => ({
CreatePersons,
CreateOrganizations,
SocialEditor,
Contacts,
RolePresenter
Contacts
}
})

View File

@ -1,10 +0,0 @@
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')};
`
}

View File

@ -14,139 +14,11 @@
// limitations under the License.
-->
<script lang="ts">
import core, { Doc, Ref } from '@anticrm/core'
import { Category, Product } from '@anticrm/inventory'
import { Panel } from '@anticrm/panel'
import {
AttributesBar,
createQuery,
getAttributePresenterClass,
getClient,
KeyedAttribute
} from '@anticrm/presentation'
import { AnyComponent, Component } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import inventory from '../plugin'
export let _id: Ref<Product>
let object: Product
let rightSection: AnyComponent | undefined
const fullSize: boolean = true
let category: Category | undefined
const client = getClient()
const hierarchy = client.getHierarchy()
const docKeys: Set<string> = new Set<string>(hierarchy.getAllAttributes(core.class.AttachedDoc).keys())
const query = createQuery()
$: _id && update(_id)
function update (id: Ref<Product>) {
query.query(inventory.class.Product, { _id: id }, (result) => {
object = result[0]
client.findOne(inventory.class.Category, { _id: object.attachedTo as Ref<Category> }).then((res) => {
category = res
})
updateKeys(['comments'])
})
}
let keys: KeyedAttribute[] = []
let collectionKeys: KeyedAttribute[] = []
import { createEventDispatcher, onMount } from 'svelte'
const dispatch = createEventDispatcher()
function filterKeys (keys: KeyedAttribute[], ignoreKeys: string[]): KeyedAttribute[] {
keys = keys.filter((k) => !docKeys.has(k.key))
keys = keys.filter((k) => !ignoreKeys.includes(k.key))
return keys
}
function getFiltredKeys (ignoreKeys: string[]): KeyedAttribute[] {
const keys = [...hierarchy.getAllAttributes(object._class).entries()]
.filter(([, value]) => value.hidden !== true)
.map(([key, attr]) => ({ key, attr }))
return filterKeys(keys, ignoreKeys)
}
function updateKeys (ignoreKeys: string[]): void {
const filtredKeys = getFiltredKeys(ignoreKeys)
keys = collectionsFilter(filtredKeys, false)
collectionKeys = collectionsFilter(filtredKeys, true)
}
function collectionsFilter (keys: KeyedAttribute[], get: boolean): KeyedAttribute[] {
const result: KeyedAttribute[] = []
for (const key of keys) {
if (isCollectionAttr(key) === get) result.push(key)
}
return result
}
function isCollectionAttr (key: KeyedAttribute): boolean {
return hierarchy.isDerived(key.attr.type._class, core.class.Collection)
}
async function getCollectionEditor (key: KeyedAttribute): Promise<AnyComponent> {
const attrClass = getAttributePresenterClass(key.attr)
const clazz = client.getHierarchy().getClass(attrClass)
const editorMixin = client.getHierarchy().as(clazz, view.mixin.AttributeEditor)
return editorMixin.editor
}
function getCollectionCounter (object: Doc, key: KeyedAttribute): number {
if (client.getHierarchy().isMixin(key.attr.attributeOf)) {
return (client.getHierarchy().as(object, key.attr.attributeOf) as any)[key.key]
}
return (object as any)[key.key] ?? 0
}
onMount(() => {
dispatch('open', { ignoreKeys: ['comments', 'number', 'title', 'customer'] })
})
</script>
{#if object !== undefined}
<Panel
icon={inventory.icon.Products}
title={object.name}
subtitle={category?.name}
{rightSection}
{fullSize}
{object}
on:close={() => {
dispatch('close')
}}
>
<div slot="subtitle">
{#if keys}
<AttributesBar {object} {keys} />
{/if}
</div>
{#each collectionKeys as collection}
<div class="collection">
{#await getCollectionEditor(collection) then is}
<Component
{is}
props={{
objectId: object._id,
_class: object._class,
space: object.space,
[collection.key]: getCollectionCounter(object, collection)
}}
/>
{/await}
</div>
{/each}
</Panel>
{/if}
<style lang="scss">
.main-editor {
display: flex;
justify-content: center;
flex-direction: column;
}
.collection + .collection {
margin-top: 3.5rem;
}
</style>

View File

@ -16,14 +16,14 @@
<script lang="ts">
import { Product } from '@anticrm/inventory'
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
import EditProduct from './EditProduct.svelte'
import inventory from '../plugin'
import { EditDoc } from '@anticrm/view-resources'
export let value: Product
function show () {
closeTooltip()
showPopup(EditProduct, { _id: value._id }, 'full')
showPopup(EditDoc, { _id: value._id, _class: value._class }, 'full')
}
</script>

View File

@ -50,9 +50,9 @@
_class={contact.class.Contact}
title={lead.string.Customer}
caption={lead.string.SelectCustomer}
bind:value={object.customer}
bind:value={object.attachedTo}
on:change={() => {
change('customer', object.customer)
change('attachedTo', object.attachedTo)
}}
/>
</Grid>

View File

@ -20,9 +20,8 @@
import type { WithLookup } from '@anticrm/core'
import type { Lead } from '@anticrm/lead'
import { ActionIcon, IconMoreH, showPopup } from '@anticrm/ui'
import { ContextMenu } from '@anticrm/view-resources'
import { ContextMenu, EditDoc } from '@anticrm/view-resources'
import lead from '../plugin'
import { EditTask } from '@anticrm/task-resources'
export let object: WithLookup<Lead>
export let draggable: boolean
@ -32,7 +31,7 @@
}
function showLead () {
showPopup(EditTask, { _id: object._id }, 'full')
showPopup(EditDoc, { _id: object._id, _class: object._class }, 'full')
}
</script>

View File

@ -17,13 +17,13 @@
import type { Lead } from '@anticrm/lead'
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
import lead from '../plugin'
import { EditTask } from '@anticrm/task-resources'
import { EditDoc } from '@anticrm/view-resources'
export let value: Lead
function show () {
closeTooltip()
showPopup(EditTask, { _id: value._id }, 'full')
showPopup(EditDoc, { _id: value._id, _class: value._class }, 'full')
}
</script>

View File

@ -114,7 +114,7 @@ export async function createWorkspace (workspace: string): Promise<[Status, Logi
}
const overrideToken = getMetadata(login.metadata.OverrideLoginToken)
const email = getMetadata(login.metadata.LoginEmail) ?? ''
const email = fetchMetadataLocalStorage(login.metadata.LoginEmail) ?? ''
if (overrideToken !== undefined) {
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
if (endpoint !== undefined) {
@ -213,7 +213,7 @@ export async function selectWorkspace (workspace: string): Promise<[Status, Logi
}
const overrideToken = getMetadata(login.metadata.OverrideLoginToken)
const email = getMetadata(login.metadata.LoginEmail) ?? ''
const email = fetchMetadataLocalStorage(login.metadata.LoginEmail) ?? ''
if (overrideToken !== undefined) {
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
if (endpoint !== undefined) {

View File

@ -17,7 +17,7 @@
import type { Applicant } from '@anticrm/recruit'
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
import { getClient } from '@anticrm/presentation'
import { EditTask } from '@anticrm/task-resources'
import { EditDoc } from '@anticrm/view-resources'
import recruit from '@anticrm/recruit'
export let value: Applicant
@ -27,7 +27,7 @@
function show () {
closeTooltip()
showPopup(EditTask, { _id: value._id }, 'full')
showPopup(EditDoc, { _id: value._id, _class: value._class }, 'full')
}
</script>

View File

@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Avatar } from '@anticrm/presentation'
import { showPopup, ActionIcon, IconMoreH } from '@anticrm/ui'
@ -23,13 +22,13 @@
import { AttachmentsPresenter } from '@anticrm/attachment-resources'
import { formatName } from '@anticrm/contact'
import ApplicationPresenter from './ApplicationPresenter.svelte'
import { EditContact } from '@anticrm/contact-resources'
import { EditDoc } from '@anticrm/view-resources'
export let object: WithLookup<Applicant>
export let draggable: boolean
function showCandidate () {
showPopup(EditContact, { _id: object.attachedTo }, 'full')
showPopup(EditDoc, { _id: object.attachedTo, _class: object.attachedToClass }, 'full')
}
</script>
@ -37,7 +36,9 @@
<div class="flex-between mb-3">
<Avatar avatar={object.$lookup?.attachedTo?.avatar} size={'medium'} />
<div class="flex-grow flex-col min-w-0 ml-2">
<div class="fs-title over-underline lines-limit-2" on:click={showCandidate}>{formatName(object.$lookup?.attachedTo?.name)}</div>
<div class="fs-title over-underline lines-limit-2" on:click={showCandidate}>
{formatName(object.$lookup?.attachedTo?.name)}
</div>
<div class="small-text lines-limit-2">{object.$lookup?.attachedTo?.title ?? ''}</div>
</div>
<div class="tool"><ActionIcon label={undefined} icon={IconMoreH} size={'small'} /></div>
@ -47,10 +48,10 @@
<div class="sm-tool-icon step-lr75">
<ApplicationPresenter value={object} />
</div>
{#if object.attachments ?? 0 > 0}
{#if (object.attachments ?? 0) > 0}
<div class="step-lr75"><AttachmentsPresenter value={object} /></div>
{/if}
{#if object.comments ?? 0 > 0}
{#if (object.comments ?? 0) > 0}
<div class="step-lr75"><CommentsPresenter value={object} /></div>
{/if}
</div>
@ -63,11 +64,15 @@
display: flex;
flex-direction: column;
padding: 1rem 1.25rem;
background-color: rgba(222, 222, 240, .06);
border-radius: .75rem;
background-color: rgba(222, 222, 240, 0.06);
border-radius: 0.75rem;
user-select: none;
&.draggable { cursor: grab; }
&.draggable {
cursor: grab;
}
}
.tool {
align-self: start;
}
.tool { align-self: start; }
</style>

View File

@ -1,120 +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 core, { Class, Doc, Ref } from '@anticrm/core'
import { Panel } from '@anticrm/panel'
import { createQuery, getAttributePresenterClass, getClient } from '@anticrm/presentation'
import type { Task } from '@anticrm/task'
import { AnyComponent, Component } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import task from '../plugin'
import TaskHeader from './TaskHeader.svelte'
import { Asset } from '@anticrm/platform'
export let _id: Ref<Task>
let object: Task
const client = getClient()
const hierarchy = client.getHierarchy()
const docKeys: Set<string> = new Set<string>(hierarchy.getAllAttributes(core.class.AttachedDoc).keys())
let keys: string[] = []
let collectionKeys: string[] = []
const query = createQuery()
$: _id &&
query.query(task.class.Task, { _id }, (result) => {
object = result[0]
})
const dispatch = createEventDispatcher()
function getFiltredKeys (ignoreKeys: string[]): string[] {
let keys = [...hierarchy.getAllAttributes(object._class).entries()]
.filter(([, value]) => value.hidden !== true)
.map(([key]) => key)
keys = keys.filter((k) => !docKeys.has(k))
keys = keys.filter((k) => !ignoreKeys.includes(k))
return keys
}
function getKeys (ignoreKeys: string[]): void {
const filtredKeys = getFiltredKeys(ignoreKeys)
keys = collectionsFilter(filtredKeys, false)
collectionKeys = collectionsFilter(filtredKeys, true)
}
function collectionsFilter (keys: string[], get: boolean): string[] {
const result: string[] = []
for (const key of keys) {
if (isCollectionAttr(key) === get) result.push(key)
}
return result
}
function isCollectionAttr (key: string): boolean {
const attribute = hierarchy.getAttribute(object._class, key)
return hierarchy.isDerived(attribute.type._class, core.class.Collection)
}
async function getEditor (_class: Ref<Class<Doc>>): Promise<AnyComponent> {
const clazz = hierarchy.getClass(_class)
const editorMixin = hierarchy.as(clazz, view.mixin.ObjectEditor)
if (editorMixin?.editor == null && clazz.extends != null) return getEditor(clazz.extends)
return editorMixin.editor
}
async function getCollectionEditor (key: string): Promise<AnyComponent> {
const attribute = hierarchy.getAttribute(object._class, key)
const attrClass = getAttributePresenterClass(attribute)
const clazz = client.getHierarchy().getClass(attrClass)
const editorMixin = client.getHierarchy().as(clazz, view.mixin.AttributeEditor)
return editorMixin.editor
}
$: icon = object && (hierarchy.getClass(object._class).icon as Asset)
$: title = object && hierarchy.getClass(object._class).label
</script>
{#if object !== undefined}
<Panel
{icon}
{title}
{object}
on:close={() => {
dispatch('close')
}}
>
<TaskHeader {object} {keys} slot="subtitle" />
{#await getEditor(object._class) then is}
<Component
{is}
props={{ object }}
on:open={(ev) => {
getKeys(ev.detail.ignoreKeys)
}}
/>
{/await}
{#each collectionKeys as collection}
<div class="mt-14">
{#await getCollectionEditor(collection) then is}
<Component {is} props={{ objectId: object._id, _class: object._class, space: object.space }} />
{/await}
</div>
{/each}
</Panel>
{/if}

View File

@ -16,12 +16,12 @@
<script lang="ts">
import contact from '@anticrm/contact'
import core, { Class, Doc, Ref, RefTo } from '@anticrm/core'
import { AttributeBarEditor, AttributesBar, getClient, UserBox } from '@anticrm/presentation'
import { AttributeBarEditor, AttributesBar, getClient, KeyedAttribute, UserBox } from '@anticrm/presentation'
import { Task } from '@anticrm/task'
import task from '../plugin'
export let object: Task
export let keys: string[]
export let keys: KeyedAttribute[]
const client = getClient()
const hierarchy = client.getHierarchy()
@ -48,7 +48,7 @@
return contact.class.Employee
}
$: filtredKeys = keys.filter((p) => p !== 'state' && p !== 'assignee' && p !== 'doneState') // todo
$: filtredKeys = keys.filter((p) => p.key !== 'state' && p.key !== 'assignee' && p.key !== 'doneState') // todo
</script>
<div class="flex-between header">

View File

@ -16,9 +16,9 @@
<script lang="ts">
import type { Issue } from '@anticrm/task'
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
import EditTask from './EditTask.svelte'
import { getClient } from '@anticrm/presentation'
import task from '../plugin'
import { EditDoc } from '@anticrm/view-resources'
export let value: Issue
@ -27,7 +27,7 @@
function show () {
closeTooltip()
showPopup(EditTask, { _id: value._id }, 'full')
showPopup(EditDoc, { _id: value._id, _class: value._class }, 'full')
}
</script>

View File

@ -30,7 +30,6 @@
export let _class: Ref<Class<Item>>
export let space: Ref<SpaceWithStates>
export let open: AnyComponent
export let search: string
export let options: FindOptions<Item> | undefined
export let config: string[]

View File

@ -13,31 +13,30 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { State } from '@anticrm/task'
import { getPlatformColor } from '@anticrm/ui'
export let value: State
</script>
{#if value}
<div class="overflow-label state-container" style="background-color: {value.color}">
<div class="overflow-label state-container" style="background-color: {getPlatformColor(value.color)}">
{value.title}
</div>
{/if}
<style lang="scss">
.state-container {
padding: .25rem .5rem;
padding: 0.25rem 0.5rem;
width: 6.25rem;
max-width: 6.25rem;
text-transform: uppercase;
text-align: center;
letter-spacing: .5px;
font-size: .625rem;
letter-spacing: 0.5px;
font-size: 0.625rem;
color: #fff;
border: 1px solid rgba(0, 0, 0, .1);
border-radius: .25rem;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.25rem;
}
</style>

View File

@ -17,6 +17,7 @@
import { Ref, SortingOrder } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation'
import task, { SpaceWithStates, State } from '@anticrm/task'
import { getPlatformColor } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
export let space: Ref<SpaceWithStates>
@ -45,7 +46,7 @@
dispatch('close', state)
}}
>
<div class="color" style="background-color: {state.color}" />
<div class="color" style="background-color: {getPlatformColor(state.color)}" />
{state.title}
</div>
{/each}

View File

@ -36,12 +36,12 @@ import Todos from './components/todos/Todos.svelte'
import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
import StatusTableView from './components/StatusTableView.svelte'
import TaskHeader from './components/TaskHeader.svelte'
export { default as KanbanTemplateEditor } from './components/kanban/KanbanTemplateEditor.svelte'
export { default as KanbanTemplateSelector } from './components/kanban/KanbanTemplateSelector.svelte'
export { default as Tasks } from './components/Tasks.svelte'
export { default as EditTask } from './components/EditTask.svelte'
async function createTask (object: Doc): Promise<void> {
showPopup(CreateTask, { parent: object._id, space: object.space })
@ -108,7 +108,8 @@ export default async (): Promise<Resources> => ({
Todos,
TodoItemPresenter,
TodoStatePresenter,
StatusTableView
StatusTableView,
TaskHeader
},
actionImpl: {
CreateTask: createTask,

View File

@ -32,6 +32,8 @@
"dependencies": {
"svelte": "^3.37.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/contact": "~0.6.2",
"@anticrm/panel": "~0.6.0",
"@anticrm/core": "~0.6.11",
"@anticrm/view": "~0.6.0",
"@anticrm/ui": "~0.6.0",

View File

@ -0,0 +1,308 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 contact, { formatName } from '@anticrm/contact'
import core, { Class, ClassifierKind, Doc, Mixin, Obj, Ref } from '@anticrm/core'
import { Panel } from '@anticrm/panel'
import { Asset, translate } from '@anticrm/platform'
import {
AttributesBar,
createQuery,
getAttributePresenterClass,
getClient,
KeyedAttribute
} from '@anticrm/presentation'
import { AnyComponent, Component, Label } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import { getMixinStyle } from '../utils'
export let _id: Ref<Doc>
export let _class: Ref<Class<Doc>>
let object: Doc
let objectClass: Class<Doc>
let parentClass: Ref<Class<Doc>>
let rightSection: AnyComponent | undefined
let fullSize: boolean = true
const client = getClient()
const hierarchy = client.getHierarchy()
const docKeys: Set<string> = new Set<string>(hierarchy.getAllAttributes(core.class.AttachedDoc).keys())
const query = createQuery()
$: _id &&
_class &&
query.query(_class, { _id }, (result) => {
object = result[0]
})
$: if (object) objectClass = hierarchy.getClass(object._class)
let selectedClass: Ref<Class<Doc>> | undefined
let prevSelected = selectedClass
let keys: KeyedAttribute[] = []
let collectionKeys: KeyedAttribute[] = []
let mixins: Mixin<Doc>[] = []
let selectedMixin: Mixin<Doc> | undefined
$: if (object && prevSelected !== object._class) {
prevSelected = object._class
selectedClass = objectClass._id
selectedMixin = undefined
parentClass = getParentClass(object._class)
mixins = getMixins()
}
const dispatch = createEventDispatcher()
function getMixins (): Mixin<Doc>[] {
const descendants = hierarchy.getDescendants(parentClass)
const mixins = descendants.filter(
(m) => hierarchy.getClass(m).kind === ClassifierKind.MIXIN && hierarchy.hasMixin(object, m)
)
return mixins.map((m) => hierarchy.getClass(m) as Mixin<Doc>)
}
function filterKeys (keys: KeyedAttribute[], ignoreKeys: string[]): KeyedAttribute[] {
keys = keys.filter((k) => !docKeys.has(k.key))
keys = keys.filter((k) => !ignoreKeys.includes(k.key))
return keys
}
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 }))
return filterKeys(keys, ignoreKeys)
}
function updateKeys (ignoreKeys: string[]): void {
const filtredKeys = getFiltredKeys(
selectedClass ?? object._class,
ignoreKeys,
selectedClass !== objectClass._id ? objectClass._id : undefined
)
keys = collectionsFilter(filtredKeys, false)
collectionKeys = collectionsFilter(filtredKeys, true)
}
function collectionsFilter (keys: KeyedAttribute[], get: boolean): KeyedAttribute[] {
const result: KeyedAttribute[] = []
for (const key of keys) {
if (isCollectionAttr(key) === get) result.push(key)
}
return result
}
function isCollectionAttr (key: KeyedAttribute): boolean {
return hierarchy.isDerived(key.attr.type._class, core.class.Collection)
}
async function getEditor (_class: Ref<Class<Doc>>): Promise<AnyComponent> {
const clazz = hierarchy.getClass(_class)
const editorMixin = hierarchy.as(clazz, view.mixin.ObjectEditor)
if (editorMixin?.editor == null && clazz.extends != null) return getEditor(clazz.extends)
return editorMixin.editor
}
async function getEditorOrDefault (
_class: Ref<Class<Doc>> | undefined,
defaultClass: Ref<Class<Doc>>
): Promise<AnyComponent> {
const editor = _class !== undefined ? await getEditor(_class) : undefined
if (editor !== undefined) {
return editor
}
return getEditor(defaultClass)
}
async function getCollectionEditor (key: KeyedAttribute): Promise<AnyComponent> {
const attrClass = getAttributePresenterClass(key.attr)
const clazz = client.getHierarchy().getClass(attrClass)
const editorMixin = client.getHierarchy().as(clazz, view.mixin.AttributeEditor)
return editorMixin.editor
}
function getIcon (_class: Ref<Class<Obj>>): Asset {
let clazz = hierarchy.getClass(_class)
if (clazz.icon !== undefined) return clazz.icon
while (clazz.extends !== undefined) {
clazz = hierarchy.getClass(clazz.extends)
if (clazz.icon !== undefined) {
return clazz.icon
}
}
throw new Error(`Icon not found for ${_class}`)
}
$: icon = object && getIcon(object._class)
function getCollectionCounter (object: Doc, key: KeyedAttribute): number {
if (hierarchy.isMixin(key.attr.attributeOf)) {
return (hierarchy.as(object, key.attr.attributeOf) as any)[key.key]
}
return (object as any)[key.key] ?? 0
}
function getParentClass (_class: Ref<Class<Doc>>): Ref<Class<Doc>> {
const baseDomain = hierarchy.getDomain(_class)
const ancestors = hierarchy.getAncestors(_class)
let result: Ref<Class<Doc>> = _class
for (const ancestor of ancestors) {
try {
const domain = hierarchy.getClass(ancestor).domain
if (domain === baseDomain) {
result = ancestor
}
} catch {}
}
return result
}
async function getTitle (object: Doc): Promise<string> {
const name = (object as any).name
if (name !== undefined) {
if (hierarchy.isDerived(object._class, contact.class.Person)) {
return formatName(name)
}
return name
}
const label = hierarchy.getClass(object._class).label
return await translate(label, {})
}
async function getHeaderEditor (_class: Ref<Class<Doc>>): Promise<AnyComponent | undefined> {
const clazz = hierarchy.getClass(_class)
const editorMixin = hierarchy.as(clazz, view.mixin.ObjectEditorHeader)
if (editorMixin.editor != null) return editorMixin.editor
if (clazz.extends != null) return getHeaderEditor(clazz.extends)
}
</script>
{#if object !== undefined}
{#await getTitle(object) then title}
<Panel
{icon}
{title}
{rightSection}
{fullSize}
{object}
on:close={() => {
dispatch('close')
}}
>
<div class="w-full" slot="subtitle">
{#await getHeaderEditor(object._class) then is}
{#if is}
<Component {is} props={{ object, keys }} />
{:else}
<AttributesBar {object} {keys} />
{/if}
{/await}
</div>
<div class="main-editor">
{#await getEditorOrDefault(selectedClass, object._class) then is}
<Component
{is}
props={{ object }}
on:open={(ev) => {
updateKeys(ev.detail.ignoreKeys)
}}
on:click={(ev) => {
fullSize = true
rightSection = ev.detail.presenter
}}
/>
{/await}
</div>
{#if mixins.length > 0}
<div class="mixin-container">
<div
class="mixin-selector"
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={getMixinStyle(mixin._id, selectedClass === mixin._id)}
on:click={() => {
selectedClass = mixin._id
selectedMixin = mixin
}}
>
<Label label={mixin.label} />
</div>
{/each}
</div>
{/if}
{#each collectionKeys as collection}
<div class="mt-14">
{#await getCollectionEditor(collection) then is}
<Component
{is}
props={{
objectId: object._id,
_class: object._class,
space: object.space,
[collection.key]: getCollectionCounter(object, collection)
}}
/>
{/await}
</div>
{/each}
</Panel>
{/await}
{/if}
<style lang="scss">
.main-editor {
display: flex;
justify-content: center;
flex-direction: column;
}
.mixin-container {
margin-top: 2rem;
display: flex;
.mixin-selector {
margin-left: 0.5rem;
cursor: pointer;
height: 1.5rem;
min-width: 5.25rem;
border-radius: 0.5rem;
font-weight: 500;
font-size: 0.625rem;
text-transform: uppercase;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@ -14,16 +14,14 @@
// limitations under the License.
-->
<script lang="ts">
import { Contact } from '@anticrm/contact'
import { ClassifierKind, Doc, Mixin } from '@anticrm/core'
import { Class, ClassifierKind, Doc, Mixin, Ref } 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
export let value: Doc
const client = getClient()
const hierarchy = client.getHierarchy()
@ -31,8 +29,20 @@
let mixins: Mixin<Doc>[] = []
$: if (value !== undefined) {
const baseDomain = hierarchy.getDomain(value._class)
const ancestors = hierarchy.getAncestors(value._class)
let parentClass: Ref<Class<Doc>> = value._class
for (const ancestor of ancestors) {
try {
const domain = hierarchy.getClass(ancestor).domain
if (domain === baseDomain) {
parentClass = ancestor
}
} catch {}
}
mixins = hierarchy
.getDescendants(contact.class.Contact)
.getDescendants(parentClass)
.filter((m) => hierarchy.getClass(m).kind === ClassifierKind.MIXIN && hierarchy.hasMixin(value, m))
.map((m) => hierarchy.getClass(m) as Mixin<Doc>)
}

View File

@ -28,10 +28,12 @@ import TableView from './components/TableView.svelte'
import TimestampPresenter from './components/TimestampPresenter.svelte'
import { deleteObject } from './utils'
import MoveView from './components/Move.svelte'
import EditDoc from './components/EditDoc.svelte'
import RolePresenter from './components/RolePresenter.svelte'
export { default as ContextMenu } from './components/Menu.svelte'
export { buildModel, getActions, getObjectPresenter, LoadingProps } from './utils'
export { Table, TableView }
export { Table, TableView, EditDoc }
function Delete (object: Doc): void {
showPopup(
@ -66,6 +68,7 @@ export default async (): Promise<Resources> => ({
TableView,
TimestampPresenter,
DateEditor,
DatePresenter
DatePresenter,
RolePresenter
}
})

View File

@ -14,13 +14,25 @@
// limitations under the License.
//
import core, { AttachedDoc, Class, Client, Collection, Doc, FindOptions, FindResult, Obj, Ref, TxOperations, matchQuery } from '@anticrm/core'
import core, {
AttachedDoc,
Class,
Client,
Collection,
Doc,
FindOptions,
FindResult,
Obj,
Ref,
TxOperations,
matchQuery
} from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { getResource } from '@anticrm/platform'
import { getAttributePresenterClass } from '@anticrm/presentation'
import type { Action, ActionTarget, BuildModelOptions } from '@anticrm/view'
import view, { AttributeModel, BuildModelKey } from '@anticrm/view'
import { ErrorPresenter } from '@anticrm/ui'
import { ErrorPresenter, getPlatformColorForText } from '@anticrm/ui'
/**
* Define some properties to be used to show component until data is properly loaded.
@ -32,7 +44,11 @@ export interface LoadingProps {
/**
* @public
*/
export async function getObjectPresenter (client: Client, _class: Ref<Class<Obj>>, preserveKey: BuildModelKey): Promise<AttributeModel> {
export async function getObjectPresenter (
client: Client,
_class: Ref<Class<Obj>>,
preserveKey: BuildModelKey
): Promise<AttributeModel> {
const clazz = client.getHierarchy().getClass(_class)
const presenterMixin = client.getHierarchy().as(clazz, view.mixin.AttributePresenter)
if (presenterMixin.presenter === undefined) {
@ -44,9 +60,8 @@ export async function getObjectPresenter (client: Client, _class: Ref<Class<Obj>
}
const presenter = await getResource(presenterMixin.presenter)
const key = preserveKey.sortingKey ?? preserveKey.key
const sortingKey = clazz.sortingKey !== undefined
? (key.length > 0 ? key + '.' + clazz.sortingKey : clazz.sortingKey)
: key
const sortingKey =
clazz.sortingKey !== undefined ? (key.length > 0 ? key + '.' + clazz.sortingKey : clazz.sortingKey) : key
return {
key: preserveKey.key,
_class,
@ -56,7 +71,12 @@ export async function getObjectPresenter (client: Client, _class: Ref<Class<Obj>
}
}
async function getAttributePresenter (client: Client, _class: Ref<Class<Obj>>, key: string, preserveKey: BuildModelKey): Promise<AttributeModel> {
async function getAttributePresenter (
client: Client,
_class: Ref<Class<Obj>>,
key: string,
preserveKey: BuildModelKey
): Promise<AttributeModel> {
const attribute = client.getHierarchy().getAttribute(_class, key)
let attrClass = getAttributePresenterClass(attribute)
const clazz = client.getHierarchy().getClass(attrClass)
@ -84,7 +104,13 @@ async function getAttributePresenter (client: Client, _class: Ref<Class<Obj>>, k
}
}
async function getPresenter (client: Client, _class: Ref<Class<Obj>>, key: BuildModelKey, preserveKey: BuildModelKey, options?: FindOptions<Doc>): Promise<AttributeModel> {
async function getPresenter (
client: Client,
_class: Ref<Class<Obj>>,
key: BuildModelKey,
preserveKey: BuildModelKey,
options?: FindOptions<Doc>
): Promise<AttributeModel> {
if (key.presenter !== undefined) {
const { presenter, label, sortingKey } = key
return {
@ -122,30 +148,37 @@ async function getPresenter (client: Client, _class: Ref<Class<Obj>>, key: Build
export async function buildModel (options: BuildModelOptions): Promise<AttributeModel[]> {
console.log('building table model for', options)
// eslint-disable-next-line array-callback-return
const model = options.keys.map(key => typeof key === 'string' ? { key: key } : key).map(async key => {
try {
return await getPresenter(options.client, options._class, key, key, options.options)
} catch (err: any) {
if ((options.ignoreMissing ?? false)) {
return undefined
const model = options.keys
.map((key) => (typeof key === 'string' ? { key: key } : key))
.map(async (key) => {
try {
return await getPresenter(options.client, options._class, key, key, options.options)
} catch (err: any) {
if (options.ignoreMissing ?? false) {
return undefined
}
const stringKey = key.label ?? key.key
console.error('Failed to find presenter for', key, err)
const errorPresenter: AttributeModel = {
key: '',
sortingKey: '',
presenter: ErrorPresenter,
label: stringKey as IntlString,
_class: core.class.TypeString,
props: { error: err }
}
return errorPresenter
}
const stringKey = key.label ?? key.key
console.error('Failed to find presenter for', key, err)
const errorPresenter: AttributeModel = {
key: '',
sortingKey: '',
presenter: ErrorPresenter,
label: stringKey as IntlString,
_class: core.class.TypeString,
props: { error: err }
}
return errorPresenter
}
})
return (await Promise.all(model)).filter(a => a !== undefined) as AttributeModel[]
})
return (await Promise.all(model)).filter((a) => a !== undefined) as AttributeModel[]
}
function filterActions (client: Client, doc: Doc, targets: ActionTarget[], derived: Ref<Class<Doc>> = core.class.Doc): Array<Ref<Action>> {
function filterActions (
client: Client,
doc: Doc,
targets: ActionTarget[],
derived: Ref<Class<Doc>> = core.class.Doc
): Array<Ref<Action>> {
const result: Array<Ref<Action>> = []
const hierarchy = client.getHierarchy()
for (const target of targets) {
@ -170,7 +203,11 @@ function filterActions (client: Client, doc: Doc, targets: ActionTarget[], deriv
* So if we have contribution for Doc, Space and we ask for SpaceWithStates and derivedFrom=Space,
* we won't recieve Doc contribution but recieve Space ones.
*/
export async function getActions (client: Client, doc: Doc, derived: Ref<Class<Doc>> = core.class.Doc): Promise<FindResult<Action>> {
export async function getActions (
client: Client,
doc: Doc,
derived: Ref<Class<Doc>> = core.class.Doc
): Promise<FindResult<Action>> {
const targets = await client.findAll(view.class.ActionTarget, {})
return await client.findAll(view.class.Action, { _id: { $in: filterActions(client, doc, targets, derived) } })
}
@ -183,14 +220,24 @@ export async function deleteObject (client: TxOperations, object: Doc): Promise<
const collection = attribute.type as Collection<AttachedDoc>
const allAttached = await client.findAll(collection.of, { attachedTo: object._id })
for (const attached of allAttached) {
deleteObject(client, attached).catch(err => console.log('failed to delete', name, err))
deleteObject(client, attached).catch((err) => console.log('failed to delete', name, err))
}
}
}
if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
const adoc = object as AttachedDoc
client.removeCollection(object._class, object.space, adoc._id, adoc.attachedTo, adoc.attachedToClass, adoc.collection).catch(err => console.error(err))
client
.removeCollection(object._class, object.space, adoc._id, adoc.attachedTo, adoc.attachedToClass, adoc.collection)
.catch((err) => console.error(err))
} else {
client.removeDoc(object._class, object.space, object._id).catch(err => console.error(err))
client.removeDoc(object._class, object.space, object._id).catch((err) => console.error(err))
}
}
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')};
`
}

View File

@ -40,6 +40,13 @@ export interface ObjectEditor extends Class<Doc> {
editor: AnyComponent
}
/**
* @public
*/
export interface ObjectEditorHeader extends Class<Doc> {
editor: AnyComponent
}
/**
* @public
*/
@ -60,7 +67,6 @@ export interface ViewletDescriptor extends Doc, UXObject {
export interface Viewlet extends Doc {
attachTo: Ref<Class<Space>>
descriptor: Ref<ViewletDescriptor>
open: AnyComponent
options?: FindOptions<Doc>
config: any
}
@ -75,7 +81,7 @@ export interface Action extends Doc, UXObject {
/**
* @public
*/
export interface ActionTarget<T extends Doc=Doc> extends Doc {
export interface ActionTarget<T extends Doc = Doc> extends Doc {
target: Ref<Class<T>>
action: Ref<Action>
@ -141,6 +147,7 @@ const view = plugin(viewId, {
AttributeEditor: '' as Ref<Mixin<AttributeEditor>>,
AttributePresenter: '' as Ref<Mixin<AttributePresenter>>,
ObjectEditor: '' as Ref<Mixin<ObjectEditor>>,
ObjectEditorHeader: '' as Ref<Mixin<ObjectEditorHeader>>,
ObjectValidator: '' as Ref<Mixin<ObjectValidator>>,
ObjectFactory: '' as Ref<Mixin<ObjectFactory>>
},

View File

@ -31,7 +31,6 @@
<Component is={viewlet.$lookup?.descriptor?.component} props={ {
_class,
space,
open: viewlet.open,
options: viewlet.options,
config: viewlet.config,
search