mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-25 22:02:23 +02:00
281 lines
8.1 KiB
Svelte
281 lines
8.1 KiB
Svelte
<!--
|
|
// Copyright © 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 core, {
|
|
AnyAttribute,
|
|
ArrOf,
|
|
AttachedDoc,
|
|
Class,
|
|
ClassifierKind,
|
|
Collection,
|
|
Doc,
|
|
EnumOf,
|
|
Ref,
|
|
RefTo,
|
|
Type
|
|
} from '@hcengineering/core'
|
|
import { getResource, IntlString } from '@hcengineering/platform'
|
|
import presentation, { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
|
|
import {
|
|
Action,
|
|
ActionIcon,
|
|
AnySvelteComponent,
|
|
CircleButton,
|
|
Component,
|
|
getEventPositionElement,
|
|
Icon,
|
|
IconAdd,
|
|
IconDelete,
|
|
IconEdit,
|
|
IconMoreV,
|
|
Label,
|
|
Menu,
|
|
showPopup
|
|
} from '@hcengineering/ui'
|
|
import view from '@hcengineering/view'
|
|
import { getContextActions } from '@hcengineering/view-resources/src/actions'
|
|
import settings from '../plugin'
|
|
import CreateAttribute from './CreateAttribute.svelte'
|
|
import EditAttribute from './EditAttribute.svelte'
|
|
import EditClassLabel from './EditClassLabel.svelte'
|
|
|
|
export let _class: Ref<Class<Doc>>
|
|
export let ofClass: Ref<Class<Doc>> | undefined
|
|
|
|
export let attributeMapper:
|
|
| {
|
|
component: AnySvelteComponent
|
|
label: IntlString
|
|
props: Record<string, any>
|
|
}
|
|
| undefined
|
|
|
|
const client = getClient()
|
|
const hierarchy = client.getHierarchy()
|
|
|
|
const classQuery = createQuery()
|
|
|
|
let clazz: Class<Doc> | undefined
|
|
|
|
$: classQuery.query(core.class.Class, { _id: _class }, (res) => {
|
|
clazz = res.shift()
|
|
})
|
|
$: attributes = getCustomAttributes(_class)
|
|
|
|
function getCustomAttributes (_class: Ref<Class<Doc>>): AnyAttribute[] {
|
|
const cl = hierarchy.getClass(_class)
|
|
const attributes = Array.from(
|
|
hierarchy.getAllAttributes(_class, _class === ofClass ? core.class.Doc : cl.extends).values()
|
|
)
|
|
// const filtred = attributes.filter((p) => !p.hidden)
|
|
return attributes
|
|
}
|
|
|
|
const attrQuery = createQuery()
|
|
|
|
$: attrQuery.query(core.class.Attribute, { attributeOf: _class }, () => {
|
|
attributes = getCustomAttributes(_class)
|
|
})
|
|
|
|
function update () {
|
|
attributes = getCustomAttributes(_class)
|
|
}
|
|
|
|
function createAttribute () {
|
|
showPopup(CreateAttribute, { _class }, 'top', update)
|
|
}
|
|
|
|
async function editAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
|
|
showPopup(EditAttribute, { attribute, exist }, 'top', update)
|
|
}
|
|
|
|
async function removeAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
|
|
showPopup(
|
|
MessageBox,
|
|
{
|
|
label: settings.string.DeleteAttribute,
|
|
message: exist ? settings.string.DeleteAttributeExistConfirm : settings.string.DeleteAttributeConfirm
|
|
},
|
|
'top',
|
|
async (result) => {
|
|
if (result) {
|
|
await client.remove(attribute)
|
|
update()
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
async function showMenu (ev: MouseEvent, attribute: AnyAttribute) {
|
|
const exist = (await client.findOne(attribute.attributeOf, { [attribute.name]: { $exists: true } })) !== undefined
|
|
|
|
const actions: Action[] = [
|
|
{
|
|
label: presentation.string.Edit,
|
|
icon: IconEdit,
|
|
action: async () => {
|
|
editAttribute(attribute, exist)
|
|
}
|
|
}
|
|
]
|
|
if (attribute.isCustom) {
|
|
actions.push({
|
|
label: presentation.string.Remove,
|
|
icon: IconDelete,
|
|
action: async () => {
|
|
removeAttribute(attribute, exist)
|
|
}
|
|
})
|
|
}
|
|
const extra = await getContextActions(client, attribute, { mode: 'context' })
|
|
actions.push(
|
|
...extra.map((it) => ({
|
|
label: it.label,
|
|
icon: it.icon,
|
|
action: async (_: any, evt: Event) => {
|
|
const r = await getResource(it.action)
|
|
r(attribute, evt, it.actionProps)
|
|
}
|
|
}))
|
|
)
|
|
showPopup(Menu, { actions }, getEventPositionElement(ev))
|
|
}
|
|
|
|
function getAttrType (type: Type<any>): IntlString | undefined {
|
|
switch (type._class) {
|
|
case core.class.RefTo:
|
|
return client.getHierarchy().getClass((type as RefTo<Doc>).to).label
|
|
case core.class.Collection:
|
|
return client.getHierarchy().getClass((type as Collection<AttachedDoc>).of).label
|
|
case core.class.ArrOf:
|
|
return (type as ArrOf<Doc>).of.label
|
|
default:
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
async function getEnumName (type: Type<any>): Promise<string | undefined> {
|
|
const ref = (type as EnumOf).of
|
|
const res = await client.findOne(core.class.Enum, { _id: ref })
|
|
return res?.name
|
|
}
|
|
function editLabel (evt: MouseEvent): void {
|
|
showPopup(EditClassLabel, { clazz }, getEventPositionElement(evt))
|
|
}
|
|
</script>
|
|
|
|
<div class="flex-row-center fs-title mb-3">
|
|
{#if clazz?.icon}
|
|
<div class="mr-2 flex">
|
|
<Icon icon={clazz.icon} size={'medium'} />
|
|
{#if clazz.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(clazz, settings.mixin.UserMixin)}
|
|
<Icon icon={IconAdd} size={'x-small'} />
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
{#if clazz}
|
|
<Label label={clazz.label} />
|
|
{#if clazz.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(clazz, settings.mixin.UserMixin)}
|
|
<div class="ml-2">
|
|
<ActionIcon icon={IconEdit} size="small" action={editLabel} />
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
</div>
|
|
<div class="flex-between trans-title mb-3">
|
|
<Label label={settings.string.Attributes} />
|
|
<CircleButton icon={IconAdd} size="medium" on:click={createAttribute} />
|
|
</div>
|
|
<table class="antiTable">
|
|
<thead class="scroller-thead">
|
|
<tr class="scroller-thead__tr">
|
|
<!-- <th>Field name</th> -->
|
|
<th>
|
|
<div class="antiTable-cells">
|
|
<Label label={settings.string.Attribute} />
|
|
</div>
|
|
</th>
|
|
<th>
|
|
<div class="antiTable-cells">
|
|
<Label label={settings.string.Type} />
|
|
</div>
|
|
</th>
|
|
<th>
|
|
<div class="antiTable-cells">
|
|
<Label label={settings.string.Visibility} />
|
|
</div>
|
|
</th>
|
|
<th>
|
|
<div class="antiTable-cells">
|
|
<Label label={settings.string.Custom} />
|
|
</div>
|
|
</th>
|
|
{#if attributeMapper}
|
|
<th>
|
|
<Label label={attributeMapper.label} />
|
|
</th>
|
|
{/if}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{#each attributes as attr}
|
|
{@const attrType = getAttrType(attr.type)}
|
|
<tr
|
|
class="antiTable-body__row"
|
|
on:contextmenu={(ev) => {
|
|
ev.preventDefault()
|
|
showMenu(ev, attr)
|
|
}}
|
|
>
|
|
<td>
|
|
<div class="antiTable-cells__firstCell whitespace-nowrap">
|
|
<Label label={attr.label} />
|
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
<div id="context-menu" class="antiTable-cells__firstCell-menuRow" on:click={(ev) => showMenu(ev, attr)}>
|
|
<IconMoreV size={'small'} />
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="select-text whitespace-nowrap">
|
|
<Label label={attr.type.label} />
|
|
{#if attrType !== undefined}
|
|
: <Label label={attrType} />
|
|
{/if}
|
|
{#if attr.type._class === core.class.EnumOf}
|
|
{#await getEnumName(attr.type) then name}
|
|
{#if name}
|
|
: {name}
|
|
{/if}
|
|
{/await}
|
|
{/if}
|
|
</td>
|
|
<td>
|
|
{#if attr.hidden}
|
|
<Label label={settings.string.Hidden} />
|
|
{/if}
|
|
</td>
|
|
<td>
|
|
<Component is={view.component.BooleanTruePresenter} props={{ value: attr.isCustom ?? false }} />
|
|
</td>
|
|
{#if attributeMapper}
|
|
<td>
|
|
<svelte:component this={attributeMapper.component} {...attributeMapper.props} attribute={attr} />
|
|
</td>
|
|
{/if}
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
</table>
|