platform/plugins/view-resources/src/components/EditDoc.svelte
Andrey Sobolev 250937c175
Fix collection editors in Safari (#973)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
2022-02-08 16:40:13 +01:00

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>