mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-22 16:27:22 +00:00
331 lines
9.8 KiB
Svelte
331 lines
9.8 KiB
Svelte
<!--
|
|
// 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, Spinner } 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>>
|
|
export let rightSection: AnyComponent | undefined = undefined
|
|
let object: Doc
|
|
let objectClass: Class<Doc>
|
|
let parentClass: Ref<Class<Doc>>
|
|
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 !== undefined) objectClass = hierarchy.getClass(object._class)
|
|
|
|
let selectedClass: Ref<Class<Doc>> | undefined
|
|
let prevSelected = selectedClass
|
|
|
|
let keys: KeyedAttribute[] = []
|
|
let collectionEditors: {key: KeyedAttribute, editor: AnyComponent} [] = []
|
|
|
|
let mixins: Mixin<Doc>[] = []
|
|
|
|
$: if (object && prevSelected !== object._class) {
|
|
prevSelected = object._class
|
|
selectedClass = objectClass._id
|
|
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)
|
|
}
|
|
|
|
let ignoreKeys: string[] = []
|
|
|
|
async function updateKeys (): Promise<void> {
|
|
const filtredKeys = getFiltredKeys(
|
|
selectedClass ?? object._class,
|
|
ignoreKeys,
|
|
selectedClass !== objectClass._id ? objectClass._id : undefined
|
|
)
|
|
keys = collectionsFilter(filtredKeys, false)
|
|
|
|
const collectionKeys = collectionsFilter(filtredKeys, true)
|
|
const editors: {key: KeyedAttribute, editor: AnyComponent}[] = []
|
|
for (const k of collectionKeys) {
|
|
const editor = await getCollectionEditor(k)
|
|
editors.push({ key: k, editor })
|
|
}
|
|
collectionEditors = editors
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
let mainEditor: AnyComponent
|
|
|
|
$: if (object) getEditorOrDefault(selectedClass, object._class)
|
|
|
|
async function getEditorOrDefault (_class: Ref<Class<Doc>> | undefined, defaultClass: Ref<Class<Doc>>): Promise<void> {
|
|
console.log('get editor or default')
|
|
let editor = _class !== undefined ? await getEditor(_class) : undefined
|
|
if (editor === undefined) {
|
|
editor = await getEditor(defaultClass)
|
|
}
|
|
mainEditor = editor
|
|
updateKeys()
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
let title: string = ''
|
|
|
|
$: if (object !== undefined) {
|
|
getTitle(object).then((t) => {
|
|
title = t
|
|
})
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
let headerEditor: AnyComponent | undefined = undefined
|
|
let headerLoading = false
|
|
$: if (object !== undefined) {
|
|
headerLoading = true
|
|
getHeaderEditor(object._class).then((r) => {
|
|
headerEditor = r
|
|
headerLoading = false
|
|
})
|
|
}
|
|
</script>
|
|
|
|
{#if object !== undefined && title !== undefined}
|
|
<Panel
|
|
{icon}
|
|
{title}
|
|
{rightSection}
|
|
{fullSize}
|
|
{object}
|
|
on:close={() => {
|
|
dispatch('close')
|
|
}}
|
|
>
|
|
<div class="w-full" slot="subtitle">
|
|
{#if !headerLoading}
|
|
{#if headerEditor !== undefined}
|
|
<Component is={headerEditor} props={{ object, keys }} />
|
|
{:else}
|
|
<AttributesBar {object} {keys} />
|
|
{/if}
|
|
{/if}
|
|
</div>
|
|
<div class="main-editor">
|
|
{#if mainEditor}
|
|
<Component
|
|
is={mainEditor}
|
|
props={{ object }}
|
|
on:open={(ev) => {
|
|
ignoreKeys = ev.detail.ignoreKeys
|
|
updateKeys()
|
|
}}
|
|
on:click={(ev) => {
|
|
fullSize = true
|
|
rightSection = ev.detail.presenter
|
|
}}
|
|
/>
|
|
{/if}
|
|
</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
|
|
}}
|
|
>
|
|
<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
|
|
}}
|
|
>
|
|
<Label label={mixin.label} />
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
{#each collectionEditors as collection, i}
|
|
<div class="mt-14">
|
|
<Component
|
|
is={collection.editor}
|
|
props={{
|
|
objectId: object._id,
|
|
_class: object._class,
|
|
space: object.space,
|
|
[collection.key.key]: getCollectionCounter(object, collection.key)
|
|
}}
|
|
/>
|
|
</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: 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>
|