mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-28 11:17:16 +00:00
Allow Mixin Add/Edit/Delete operations (#2247)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
00131ed0a3
commit
ae09613d5f
@ -13,15 +13,23 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Builder, Mixin, Model } from '@anticrm/model'
|
||||
import { Ref, Domain, DOMAIN_MODEL } from '@anticrm/core'
|
||||
import core, { TClass, TDoc } from '@anticrm/model-core'
|
||||
import setting from './plugin'
|
||||
import { Editable, Integration, IntegrationType, Handler, SettingsCategory, settingId } from '@anticrm/setting'
|
||||
import type { Asset, IntlString } from '@anticrm/platform'
|
||||
import task from '@anticrm/task'
|
||||
import activity from '@anticrm/activity'
|
||||
import view from '@anticrm/view'
|
||||
import { Domain, DOMAIN_MODEL, Ref } from '@anticrm/core'
|
||||
import { Builder, Mixin, Model } from '@anticrm/model'
|
||||
import core, { TClass, TDoc } from '@anticrm/model-core'
|
||||
import view, { createAction } from '@anticrm/model-view'
|
||||
import type { Asset, IntlString } from '@anticrm/platform'
|
||||
import {
|
||||
Editable,
|
||||
Handler,
|
||||
Integration,
|
||||
IntegrationType,
|
||||
settingId,
|
||||
SettingsCategory,
|
||||
UserMixin
|
||||
} from '@anticrm/setting'
|
||||
import task from '@anticrm/task'
|
||||
import setting from './plugin'
|
||||
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
@ -65,8 +73,18 @@ export class TIntegrationType extends TDoc implements IntegrationType {
|
||||
@Mixin(setting.mixin.Editable, core.class.Class)
|
||||
export class TEditable extends TClass implements Editable {}
|
||||
|
||||
@Mixin(setting.mixin.UserMixin, core.class.Class)
|
||||
export class TUserMixin extends TClass implements UserMixin {}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TIntegration, TIntegrationType, TSettingsCategory, TWorkspaceSettingCategory, TEditable)
|
||||
builder.createModel(
|
||||
TIntegration,
|
||||
TIntegrationType,
|
||||
TSettingsCategory,
|
||||
TWorkspaceSettingCategory,
|
||||
TEditable,
|
||||
TUserMixin
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
setting.class.SettingsCategory,
|
||||
@ -276,6 +294,44 @@ export function createModel (builder: Builder): void {
|
||||
builder.mixin(core.class.EnumOf, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: setting.component.EnumTypeEditor
|
||||
})
|
||||
|
||||
builder.mixin(core.class.Class, core.class.Class, view.mixin.IgnoreActions, {
|
||||
actions: [view.action.Delete]
|
||||
})
|
||||
|
||||
createAction(builder, {
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: setting.component.CreateMixin,
|
||||
fillProps: {
|
||||
_object: 'value'
|
||||
}
|
||||
},
|
||||
label: setting.string.CreateMixin,
|
||||
input: 'focus',
|
||||
icon: view.icon.Pin,
|
||||
category: setting.category.Settings,
|
||||
target: core.class.Class,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'edit'
|
||||
}
|
||||
})
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: setting.actionImpl.DeleteMixin,
|
||||
label: view.string.Delete,
|
||||
icon: view.icon.Delete,
|
||||
keyBinding: ['Meta + Backspace', 'Ctrl + Backspace'],
|
||||
category: view.category.General,
|
||||
input: 'any',
|
||||
target: setting.mixin.UserMixin,
|
||||
context: { mode: ['context', 'browser'], group: 'tools' }
|
||||
},
|
||||
setting.action.DeleteMixin
|
||||
)
|
||||
}
|
||||
|
||||
export { settingOperation } from './migration'
|
||||
|
@ -13,12 +13,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { TxViewlet } from '@anticrm/activity'
|
||||
import { Doc, Ref } from '@anticrm/core'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import { settingId } from '@anticrm/setting'
|
||||
import setting from '@anticrm/setting-resources/src/plugin'
|
||||
import type { TxViewlet } from '@anticrm/activity'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
import { Action, ActionCategory, ViewAction } from '@anticrm/view'
|
||||
|
||||
export default mergeIds(settingId, setting, {
|
||||
activity: {
|
||||
@ -37,6 +38,16 @@ export default mergeIds(settingId, setting, {
|
||||
DateTypeEditor: '' as AnyComponent,
|
||||
RefEditor: '' as AnyComponent,
|
||||
EnumTypeEditor: '' as AnyComponent,
|
||||
Owners: '' as AnyComponent
|
||||
Owners: '' as AnyComponent,
|
||||
CreateMixin: '' as AnyComponent
|
||||
},
|
||||
category: {
|
||||
Settings: '' as Ref<ActionCategory>
|
||||
},
|
||||
action: {
|
||||
DeleteMixin: '' as Ref<Action>
|
||||
},
|
||||
actionImpl: {
|
||||
DeleteMixin: '' as ViewAction<Record<string, any>>
|
||||
}
|
||||
})
|
||||
|
@ -144,8 +144,8 @@ export class Hierarchy {
|
||||
) {
|
||||
const _id = tx.objectId as Ref<Classifier>
|
||||
this.classifiers.set(_id, TxProcessor.createDoc2Doc(tx as TxCreateDoc<Classifier>))
|
||||
this.addAncestors(_id)
|
||||
this.addDescendant(_id)
|
||||
this.updateAncestors(_id)
|
||||
this.updateDescendant(_id)
|
||||
} else if (tx.objectClass === core.class.Attribute) {
|
||||
const createTx = tx as TxCreateDoc<AnyAttribute>
|
||||
this.addAttribute(TxProcessor.createDoc2Doc(createTx))
|
||||
@ -158,6 +158,11 @@ export class Hierarchy {
|
||||
const doc = this.attributesById.get(updateTx.objectId)
|
||||
if (doc === undefined) return
|
||||
this.addAttribute(TxProcessor.updateDoc2Doc(doc, updateTx))
|
||||
} else if (tx.objectClass === core.class.Mixin || tx.objectClass === core.class.Class) {
|
||||
const updateTx = tx as TxUpdateDoc<Mixin<Class<Doc>>>
|
||||
const doc = this.classifiers.get(updateTx.objectId)
|
||||
if (doc === undefined) return
|
||||
TxProcessor.updateDoc2Doc(doc, updateTx)
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,6 +174,11 @@ export class Hierarchy {
|
||||
const map = this.attributes.get(doc.attributeOf)
|
||||
map?.delete(doc.name)
|
||||
this.attributesById.delete(removeTx.objectId)
|
||||
} else if (tx.objectClass === core.class.Mixin) {
|
||||
const removeTx = tx as TxRemoveDoc<Mixin<Class<Doc>>>
|
||||
this.updateDescendant(removeTx.objectId, false)
|
||||
this.updateAncestors(removeTx.objectId, false)
|
||||
this.classifiers.delete(removeTx.objectId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,19 +256,28 @@ export class Hierarchy {
|
||||
return data
|
||||
}
|
||||
|
||||
private addDescendant (_class: Ref<Classifier>): void {
|
||||
private updateDescendant (_class: Ref<Classifier>, add = true): void {
|
||||
const hierarchy = this.getAncestors(_class)
|
||||
for (const cls of hierarchy) {
|
||||
const list = this.descendants.get(cls)
|
||||
if (list === undefined) {
|
||||
if (add) {
|
||||
this.descendants.set(cls, [_class])
|
||||
}
|
||||
} else {
|
||||
if (add) {
|
||||
list.push(_class)
|
||||
} else {
|
||||
const pos = list.indexOf(_class)
|
||||
if (pos !== -1) {
|
||||
list.splice(pos, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addAncestors (_class: Ref<Classifier>): void {
|
||||
private updateAncestors (_class: Ref<Classifier>, add = true): void {
|
||||
const cl: Ref<Classifier>[] = [_class]
|
||||
const visited = new Set<Ref<Classifier>>()
|
||||
while (cl.length > 0) {
|
||||
@ -266,9 +285,18 @@ export class Hierarchy {
|
||||
if (addNew(visited, classifier)) {
|
||||
const list = this.ancestors.get(_class)
|
||||
if (list === undefined) {
|
||||
if (add) {
|
||||
this.ancestors.set(_class, [classifier])
|
||||
}
|
||||
} else {
|
||||
if (add) {
|
||||
addIf(list, classifier)
|
||||
} else {
|
||||
const pos = list.indexOf(classifier)
|
||||
if (pos !== -1) {
|
||||
list.splice(pos, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
cl.push(...this.ancestorsOf(classifier))
|
||||
}
|
||||
|
@ -84,8 +84,10 @@
|
||||
<div style="padding: .75rem 1.5rem">
|
||||
{#if $$slots.actions}
|
||||
<div class="flex-row-center pb-3 bottom-divider">
|
||||
{#if $$slots['actions-label']}
|
||||
<span class="fs-bold w-24 mr-6"><slot name="actions-label" /></span>
|
||||
<div class="buttons-group xsmall-gap">
|
||||
{/if}
|
||||
<div class="buttons-group xsmall-gap flex flex-grow">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -325,3 +325,8 @@ export function getPopupPositionElement (
|
||||
|
||||
return undefined
|
||||
}
|
||||
export function getEventPositionElement (evt: MouseEvent): PopupAlignment | undefined {
|
||||
return {
|
||||
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: evt.clientX, y: evt.clientY })
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import type { Kanban, SpaceWithStates, State } from '@anticrm/task'
|
||||
import task, { calcRank } from '@anticrm/task'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import { getEventPositionElement, showPopup } from '@anticrm/ui'
|
||||
import {
|
||||
ActionContext,
|
||||
ContextMenu,
|
||||
@ -31,8 +31,8 @@
|
||||
} from '@anticrm/view-resources'
|
||||
import { onMount } from 'svelte'
|
||||
import AddCard from './add-card/AddCard.svelte'
|
||||
import KanbanCard from './KanbanCard.svelte'
|
||||
import AddPanel from './AddPanel.svelte'
|
||||
import KanbanCard from './KanbanCard.svelte'
|
||||
import ListHeader from './ListHeader.svelte'
|
||||
|
||||
export let _class: Ref<Class<Card>>
|
||||
@ -93,13 +93,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
showPopup(
|
||||
ContextMenu,
|
||||
{ object },
|
||||
{
|
||||
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY })
|
||||
}
|
||||
)
|
||||
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,19 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Button, Component, getPlatformColor, IconMoreV, showPopup } from '@anticrm/ui'
|
||||
import { State } from '@anticrm/task'
|
||||
import notification from '@anticrm/notification'
|
||||
import { State } from '@anticrm/task'
|
||||
import { Button, Component, getEventPositionElement, getPlatformColor, IconMoreV, showPopup } from '@anticrm/ui'
|
||||
import { ContextMenu } from '@anticrm/view-resources'
|
||||
export let state: State
|
||||
|
||||
const showMenu = async (ev: MouseEvent): Promise<void> => {
|
||||
ev.preventDefault()
|
||||
showPopup(
|
||||
ContextMenu,
|
||||
{ object: state },
|
||||
{
|
||||
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY })
|
||||
}
|
||||
)
|
||||
showPopup(ContextMenu, { object: state }, getEventPositionElement(ev))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -68,11 +68,23 @@ class Connection implements ClientConnection {
|
||||
this.websocket?.close()
|
||||
}
|
||||
|
||||
delay = 1
|
||||
private async waitOpenConnection (): Promise<ClientSocket> {
|
||||
while (true) {
|
||||
try {
|
||||
return await this.openConnection()
|
||||
const conn = await this.openConnection()
|
||||
this.delay = 1
|
||||
return conn
|
||||
} catch (err: any) {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
console.log(`delay ${this.delay} second`)
|
||||
resolve(null)
|
||||
if (this.delay !== 5) {
|
||||
this.delay++
|
||||
}
|
||||
}, this.delay * 1000)
|
||||
})
|
||||
console.log('failed to connect', err)
|
||||
if (err.code === UNAUTHORIZED.code) {
|
||||
this.onUnauthorized?.()
|
||||
|
@ -18,7 +18,16 @@
|
||||
import { Ref, WithLookup } from '@anticrm/core'
|
||||
import { Department, Staff } from '@anticrm/hr'
|
||||
import { Avatar, getClient, UsersPopup } from '@anticrm/presentation'
|
||||
import { Button, closeTooltip, eventToHTMLElement, IconAdd, Label, showPanel, showPopup } from '@anticrm/ui'
|
||||
import {
|
||||
Button,
|
||||
closeTooltip,
|
||||
eventToHTMLElement,
|
||||
getEventPositionElement,
|
||||
IconAdd,
|
||||
Label,
|
||||
showPanel,
|
||||
showPopup
|
||||
} from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import { Menu } from '@anticrm/view-resources'
|
||||
import hr from '../plugin'
|
||||
@ -70,13 +79,7 @@
|
||||
}
|
||||
|
||||
function showMenu (e: MouseEvent) {
|
||||
showPopup(
|
||||
Menu,
|
||||
{ object: value, baseMenuClass: value._class },
|
||||
{
|
||||
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: e.clientX, y: e.clientY })
|
||||
}
|
||||
)
|
||||
showPopup(Menu, { object: value, baseMenuClass: value._class }, getEventPositionElement(e))
|
||||
}
|
||||
|
||||
function edit (e: MouseEvent): void {
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { EmployeePresenter } from '@anticrm/contact-resources'
|
||||
import { WithLookup } from '@anticrm/core'
|
||||
import { Staff } from '@anticrm/hr'
|
||||
import { closeTooltip, showPopup } from '@anticrm/ui'
|
||||
import { closeTooltip, getEventPositionElement, showPopup } from '@anticrm/ui'
|
||||
import { ContextMenu } from '@anticrm/view-resources'
|
||||
import hr from '../plugin'
|
||||
|
||||
@ -36,11 +36,7 @@
|
||||
}
|
||||
|
||||
function showContextMenu (ev: MouseEvent, object: Employee) {
|
||||
showPopup(
|
||||
ContextMenu,
|
||||
{ object },
|
||||
{ getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY }) }
|
||||
)
|
||||
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -35,6 +35,9 @@
|
||||
"DeleteAttribute": "Delete attribute",
|
||||
"DeleteAttributeConfirm": "Do you want to delete this attribute?",
|
||||
"DeleteAttributeExistConfirm": "Do you want to delete this attribute? Data will be lost",
|
||||
"DeleteMixin": "Delete Mixin",
|
||||
"DeleteMixinConfirm": "Do you want to delete this mixin?",
|
||||
"DeleteMixinExistConfirm": "Do you wany yo delete this mixin? Data will not be available",
|
||||
"Attribute": "Attribute",
|
||||
"Custom": "Custom",
|
||||
"Type": "Type",
|
||||
@ -56,6 +59,9 @@
|
||||
"Role": "Role",
|
||||
"FailedToSave": "Failed to update password",
|
||||
"ImportEnum": "Import enum values",
|
||||
"ImportEnumCopy": "Copy enum values from clipboard"
|
||||
"ImportEnumCopy": "Copy enum values from clipboard",
|
||||
"CreateMixin": "Create Mixin",
|
||||
"OldNames": "Old values",
|
||||
"NewClassName": "Type new class name or select from previous values..."
|
||||
}
|
||||
}
|
@ -56,6 +56,9 @@
|
||||
"Role": "Роль",
|
||||
"FailedToSave": "Не удалось обновить пароль",
|
||||
"ImportEnum": "Загрузить значения справочника",
|
||||
"ImportEnumCopy": "Загрузить значения справочника из буфера обмена"
|
||||
"ImportEnumCopy": "Загрузить значения справочника из буфера обмена",
|
||||
"CreateMixin": "Создать Миксин",
|
||||
"OldNames": "Предыдушие значения",
|
||||
"NewClassName": "Введите новое имя класса или выберете прошлое значение..."
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
ArrOf,
|
||||
AttachedDoc,
|
||||
Class,
|
||||
ClassifierKind,
|
||||
Collection,
|
||||
Doc,
|
||||
EnumOf,
|
||||
@ -26,11 +27,14 @@
|
||||
Type
|
||||
} from '@anticrm/core'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import presentation, { getClient, MessageBox } from '@anticrm/presentation'
|
||||
import presentation, { createQuery, getClient, MessageBox } from '@anticrm/presentation'
|
||||
import {
|
||||
Action,
|
||||
ActionIcon,
|
||||
CircleButton,
|
||||
Component,
|
||||
getEventPositionElement,
|
||||
Icon,
|
||||
IconAdd,
|
||||
IconDelete,
|
||||
IconEdit,
|
||||
@ -40,14 +44,22 @@
|
||||
showPopup
|
||||
} from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import setting from '../plugin'
|
||||
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>>
|
||||
|
||||
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[] {
|
||||
@ -73,8 +85,8 @@
|
||||
showPopup(
|
||||
MessageBox,
|
||||
{
|
||||
label: setting.string.DeleteAttribute,
|
||||
message: exist ? setting.string.DeleteAttributeExistConfirm : setting.string.DeleteAttributeConfirm
|
||||
label: settings.string.DeleteAttribute,
|
||||
message: exist ? settings.string.DeleteAttributeExistConfirm : settings.string.DeleteAttributeConfirm
|
||||
},
|
||||
'top',
|
||||
async (result) => {
|
||||
@ -105,14 +117,7 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
showPopup(
|
||||
Menu,
|
||||
{ actions },
|
||||
{
|
||||
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY })
|
||||
},
|
||||
() => {}
|
||||
)
|
||||
showPopup(Menu, { actions }, getEventPositionElement(ev), () => {})
|
||||
}
|
||||
|
||||
function getAttrType (type: Type<any>): IntlString | undefined {
|
||||
@ -133,10 +138,31 @@
|
||||
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={setting.string.Attributes} />
|
||||
<Label label={settings.string.Attributes} />
|
||||
<CircleButton icon={IconAdd} size="medium" on:click={createAttribute} />
|
||||
</div>
|
||||
<table class="antiTable">
|
||||
@ -144,17 +170,17 @@
|
||||
<tr class="scroller-thead__tr">
|
||||
<th>
|
||||
<div class="antiTable-cells">
|
||||
<Label label={setting.string.Attribute} />
|
||||
<Label label={settings.string.Attribute} />
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="antiTable-cells">
|
||||
<Label label={setting.string.Type} />
|
||||
<Label label={settings.string.Type} />
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="antiTable-cells">
|
||||
<Label label={setting.string.Custom} />
|
||||
<Label label={settings.string.Custom} />
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
|
@ -15,8 +15,10 @@
|
||||
<script lang="ts">
|
||||
import { Class, ClassifierKind, Doc, Ref } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Icon, Label } from '@anticrm/ui'
|
||||
import { getEventPositionElement, Icon, IconAdd, Label, showPopup } from '@anticrm/ui'
|
||||
import { ContextMenu } from '@anticrm/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import settings from '../plugin'
|
||||
|
||||
export let classes: Ref<Class<Doc>>[] = ['contact:class:Contact' as Ref<Class<Doc>>]
|
||||
export let _class: Ref<Class<Doc>> | undefined
|
||||
@ -41,6 +43,9 @@
|
||||
}
|
||||
return result
|
||||
}
|
||||
function showContextMenu (evt: MouseEvent, clazz: Class<Doc>): void {
|
||||
showPopup(ContextMenu, { object: clazz }, getEventPositionElement(evt))
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each classes as cl}
|
||||
@ -52,11 +57,15 @@
|
||||
on:click={() => {
|
||||
dispatch('select', cl)
|
||||
}}
|
||||
on:contextmenu|preventDefault|stopPropagation={(evt) => showContextMenu(evt, clazz)}
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
{#if clazz.icon}
|
||||
<div class="mr-2">
|
||||
<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}
|
||||
<span class="overflow-label content-accent-color"><Label label={clazz.label} /></span>
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { Class, Doc, Ref } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { getCurrentLocation, Icon, Label, navigate } from '@anticrm/ui'
|
||||
import setting from '../plugin'
|
||||
import ClassAttributes from './ClassAttributes.svelte'
|
||||
@ -32,11 +32,14 @@
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
const classes = hierarchy
|
||||
.getDescendants(core.class.Doc)
|
||||
.map((p) => hierarchy.getClass(p))
|
||||
.filter((p) => hierarchy.hasMixin(p, setting.mixin.Editable))
|
||||
const clQuery = createQuery()
|
||||
|
||||
let classes: Ref<Class<Doc>>[] = []
|
||||
clQuery.query(core.class.Class, {}, (res) => {
|
||||
classes = res
|
||||
.filter((p) => hierarchy.hasMixin(p, setting.mixin.Editable) && !hierarchy.hasMixin(p, setting.mixin.UserMixin))
|
||||
.map((p) => p._id)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="antiComponent">
|
||||
|
63
plugins/setting-resources/src/components/CreateMixin.svelte
Normal file
63
plugins/setting-resources/src/components/CreateMixin.svelte
Normal file
@ -0,0 +1,63 @@
|
||||
<!--
|
||||
// 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, { Class, ClassifierKind, Data, Doc, Mixin } from '@anticrm/core'
|
||||
import { getEmbeddedLabel } from '@anticrm/platform'
|
||||
import { Card, getClient } from '@anticrm/presentation'
|
||||
import { EditBox, Icon, Label } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import setting from '../plugin'
|
||||
|
||||
export let value: Class<Doc>
|
||||
let name: string
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function save (): Promise<void> {
|
||||
const data: Data<Mixin<Class<Doc>>> = {
|
||||
extends: value._id,
|
||||
label: getEmbeddedLabel(name),
|
||||
kind: ClassifierKind.MIXIN,
|
||||
icon: value.icon
|
||||
}
|
||||
const id = await client.createDoc(core.class.Mixin, core.space.Model, data)
|
||||
await client.createMixin(id, core.class.Mixin, core.space.Model, setting.mixin.Editable, {})
|
||||
await client.createMixin(id, core.class.Mixin, core.space.Model, setting.mixin.UserMixin, {})
|
||||
dispatch('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={setting.string.CreateMixin}
|
||||
okAction={save}
|
||||
canSave={!(name === undefined || name.trim().length === 0)}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="header">
|
||||
<div class="flex-row-center">
|
||||
{#if value.icon}
|
||||
<Icon icon={value.icon} size={'large'} />
|
||||
{/if}
|
||||
<div class="ml-2">
|
||||
<Label label={value.label} />
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="mb-2"><EditBox focus bind:value={name} placeholder={core.string.Name} maxWidth="13rem" /></div>
|
||||
</Card>
|
@ -0,0 +1,88 @@
|
||||
<!--
|
||||
// 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, { Class, Doc, DocumentUpdate, Tx, TxCreateDoc, TxUpdateDoc } from '@anticrm/core'
|
||||
import { getEmbeddedLabel, IntlString } from '@anticrm/platform'
|
||||
import presentation, { Card, createQuery, getClient } from '@anticrm/presentation'
|
||||
import { Button, EditBox, Label } from '@anticrm/ui'
|
||||
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import setting from '../plugin'
|
||||
|
||||
export let clazz: Class<Doc>
|
||||
let name: string
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function save (newLabel: IntlString): Promise<void> {
|
||||
const update: DocumentUpdate<Class<Doc>> = {}
|
||||
if (newLabel !== clazz.label) {
|
||||
update.label = newLabel
|
||||
}
|
||||
await client.updateDoc(clazz._class, clazz.space, clazz._id, update)
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
let transactions: Tx[] = []
|
||||
|
||||
query.query(
|
||||
core.class.Tx,
|
||||
{
|
||||
objectId: clazz._id
|
||||
},
|
||||
(data) => {
|
||||
transactions = data
|
||||
}
|
||||
)
|
||||
|
||||
$: labels = transactions
|
||||
.map((it) => {
|
||||
if (it._class === core.class.TxCreateDoc) {
|
||||
return (it as TxCreateDoc<Class<Doc>>).attributes.label
|
||||
}
|
||||
if (it._class === core.class.TxUpdateDoc) {
|
||||
return (it as TxUpdateDoc<Class<Doc>>).operations.label
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
.filter((it) => it !== undefined)
|
||||
.filter((it, idx, arr) => arr.indexOf(it) === idx) as IntlString[]
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={clazz.label}
|
||||
okLabel={presentation.string.Save}
|
||||
okAction={() => save(getEmbeddedLabel(name))}
|
||||
canSave={!(name === undefined || name.trim().length === 0)}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<div class="mb-2">
|
||||
<EditBox bind:value={name} placeholder={setting.string.NewClassName} maxWidth="23rem" />
|
||||
</div>
|
||||
<Label label={setting.string.OldNames} />
|
||||
<div class="flex-col">
|
||||
{#each labels as label}
|
||||
<div class="mr-4 flex-row-center gap-2 mt-1">
|
||||
<Button label={setting.string.Select} kind={'link'} on:click={() => save(label)} size={'small'} />
|
||||
<Label {label} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</Card>
|
@ -13,28 +13,56 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Class, Doc, Mixin } from '@anticrm/core'
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import Profile from './components/Profile.svelte'
|
||||
import Password from './components/Password.svelte'
|
||||
import WorkspaceSettings from './components/WorkspaceSettings.svelte'
|
||||
import Integrations from './components/Integrations.svelte'
|
||||
import ManageStatuses from './components/statuses/ManageStatuses.svelte'
|
||||
import Support from './components/Support.svelte'
|
||||
import Privacy from './components/Privacy.svelte'
|
||||
import Terms from './components/Terms.svelte'
|
||||
import Settings from './components/Settings.svelte'
|
||||
import ClassSetting from './components/ClassSetting.svelte'
|
||||
import { getClient, MessageBox } from '@anticrm/presentation'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import { deleteObject } from '@anticrm/view-resources/src/utils'
|
||||
import TxIntegrationDisable from './components/activity/TxIntegrationDisable.svelte'
|
||||
import TxIntegrationDisableReconnect from './components/activity/TxIntegrationDisableReconnect.svelte'
|
||||
import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte'
|
||||
import BooleanTypeEditor from './components/typeEditors/BooleanTypeEditor.svelte'
|
||||
import DateTypeEditor from './components/typeEditors/DateTypeEditor.svelte'
|
||||
import NumberTypeEditor from './components/typeEditors/NumberTypeEditor.svelte'
|
||||
import RefEditor from './components/typeEditors/RefEditor.svelte'
|
||||
import EnumTypeEditor from './components/typeEditors/EnumTypeEditor.svelte'
|
||||
import ClassSetting from './components/ClassSetting.svelte'
|
||||
import CreateMixin from './components/CreateMixin.svelte'
|
||||
import EditEnum from './components/EditEnum.svelte'
|
||||
import EnumSetting from './components/EnumSetting.svelte'
|
||||
import Integrations from './components/Integrations.svelte'
|
||||
import Owners from './components/Owners.svelte'
|
||||
import Password from './components/Password.svelte'
|
||||
import Privacy from './components/Privacy.svelte'
|
||||
import Profile from './components/Profile.svelte'
|
||||
import Settings from './components/Settings.svelte'
|
||||
import ManageStatuses from './components/statuses/ManageStatuses.svelte'
|
||||
import Support from './components/Support.svelte'
|
||||
import Terms from './components/Terms.svelte'
|
||||
import BooleanTypeEditor from './components/typeEditors/BooleanTypeEditor.svelte'
|
||||
import DateTypeEditor from './components/typeEditors/DateTypeEditor.svelte'
|
||||
import EnumTypeEditor from './components/typeEditors/EnumTypeEditor.svelte'
|
||||
import NumberTypeEditor from './components/typeEditors/NumberTypeEditor.svelte'
|
||||
import RefEditor from './components/typeEditors/RefEditor.svelte'
|
||||
import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte'
|
||||
import WorkspaceSettings from './components/WorkspaceSettings.svelte'
|
||||
import setting from './plugin'
|
||||
|
||||
async function DeleteMixin (object: Mixin<Class<Doc>>): Promise<void> {
|
||||
const docs = await getClient().findAll(object._id, {}, { limit: 1 })
|
||||
|
||||
showPopup(
|
||||
MessageBox,
|
||||
{
|
||||
label: setting.string.DeleteMixin,
|
||||
message: docs.length > 0 ? setting.string.DeleteMixinExistConfirm : setting.string.DeleteMixinConfirm,
|
||||
params: { count: docs.length }
|
||||
},
|
||||
undefined,
|
||||
(result?: boolean) => {
|
||||
if (result === true) {
|
||||
const objs = Array.isArray(object) ? object : [object]
|
||||
for (const o of objs) {
|
||||
deleteObject(getClient(), o).catch((err) => console.error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
activity: {
|
||||
@ -60,6 +88,10 @@ export default async (): Promise<Resources> => ({
|
||||
EnumTypeEditor,
|
||||
EditEnum,
|
||||
EnumSetting,
|
||||
Owners
|
||||
Owners,
|
||||
CreateMixin
|
||||
},
|
||||
actionImpl: {
|
||||
DeleteMixin
|
||||
}
|
||||
})
|
||||
|
@ -30,6 +30,9 @@ export default mergeIds(settingId, setting, {
|
||||
DeleteAttribute: '' as IntlString,
|
||||
DeleteAttributeConfirm: '' as IntlString,
|
||||
DeleteAttributeExistConfirm: '' as IntlString,
|
||||
DeleteMixin: '' as IntlString,
|
||||
DeleteMixinConfirm: '' as IntlString,
|
||||
DeleteMixinExistConfirm: '' as IntlString,
|
||||
Attribute: '' as IntlString,
|
||||
Attributes: '' as IntlString,
|
||||
Custom: '' as IntlString,
|
||||
@ -50,6 +53,9 @@ export default mergeIds(settingId, setting, {
|
||||
Role: '' as IntlString,
|
||||
FailedToSave: '' as IntlString,
|
||||
ImportEnum: '' as IntlString,
|
||||
ImportEnumCopy: '' as IntlString
|
||||
ImportEnumCopy: '' as IntlString,
|
||||
CreateMixin: '' as IntlString,
|
||||
OldNames: '' as IntlString,
|
||||
NewClassName: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -49,6 +49,13 @@ export interface Integration extends Doc {
|
||||
*/
|
||||
export interface Editable extends Class<Doc> {}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Mixin to allow delete of Custom classes.
|
||||
*/
|
||||
export interface UserMixin extends Class<Doc> {}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -84,7 +91,8 @@ export default plugin(settingId, {
|
||||
Owners: '' as Ref<Doc>
|
||||
},
|
||||
mixin: {
|
||||
Editable: '' as Ref<Mixin<Editable>>
|
||||
Editable: '' as Ref<Mixin<Editable>>,
|
||||
UserMixin: '' as Ref<Mixin<UserMixin>>
|
||||
},
|
||||
class: {
|
||||
SettingsCategory: '' as Ref<Class<SettingsCategory>>,
|
||||
|
@ -20,7 +20,7 @@
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import type { Kanban, SpaceWithStates, State, Task } from '@anticrm/task'
|
||||
import task from '@anticrm/task'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import { getEventPositionElement, showPopup } from '@anticrm/ui'
|
||||
import {
|
||||
ActionContext,
|
||||
FilterBar,
|
||||
@ -82,16 +82,9 @@
|
||||
|
||||
const showMenu = async (ev: MouseEvent, items: Doc[]): Promise<void> => {
|
||||
ev.preventDefault()
|
||||
showPopup(
|
||||
Menu,
|
||||
{ object: items, baseMenuClass },
|
||||
{
|
||||
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY })
|
||||
},
|
||||
() => {
|
||||
showPopup(Menu, { object: items, baseMenuClass }, getEventPositionElement(ev), () => {
|
||||
// selection = undefined
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
const onContent = (evt: any) => {
|
||||
listProvider.update(evt.detail)
|
||||
|
@ -15,18 +15,27 @@
|
||||
<script lang="ts">
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import { Class, Doc, FindOptions, getObjectValue, Ref, WithLookup } from '@anticrm/core'
|
||||
import notification from '@anticrm/notification'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
||||
import { Button, CheckBox, Component, eventToHTMLElement, IconAdd, showPopup, Spinner, tooltip } from '@anticrm/ui'
|
||||
import {
|
||||
Button,
|
||||
CheckBox,
|
||||
Component,
|
||||
eventToHTMLElement,
|
||||
ExpandCollapse,
|
||||
getEventPositionElement,
|
||||
IconAdd,
|
||||
showPopup,
|
||||
Spinner,
|
||||
tooltip
|
||||
} from '@anticrm/ui'
|
||||
import { AttributeModel, BuildModelKey } from '@anticrm/view'
|
||||
import { buildModel, getObjectPresenter, LoadingProps, Menu } from '@anticrm/view-resources'
|
||||
import { buildModel, FixedColumn, getObjectPresenter, LoadingProps, Menu } from '@anticrm/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
import { IssuesGroupByKeys, issuesGroupEditorMap, IssuesOrderByKeys, issuesSortOrderMap } from '../../utils'
|
||||
import CreateIssue from '../CreateIssue.svelte'
|
||||
import notification from '@anticrm/notification'
|
||||
import { FixedColumn } from '@anticrm/view-resources'
|
||||
import { ExpandCollapse } from '@anticrm/ui'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let currentSpace: Ref<Team> | undefined = undefined
|
||||
@ -78,16 +87,9 @@
|
||||
|
||||
const items = selectedObjectIds.length > 0 ? selectedObjectIds : object
|
||||
|
||||
showPopup(
|
||||
Menu,
|
||||
{ object: items, baseMenuClass },
|
||||
{
|
||||
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: event.clientX, y: event.clientY })
|
||||
},
|
||||
() => {
|
||||
showPopup(Menu, { object: items, baseMenuClass }, getEventPositionElement(event), () => {
|
||||
selectedRowIndex = undefined
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export const onObjectChecked = (docs: Doc[], value: boolean) => {
|
||||
|
@ -18,8 +18,18 @@
|
||||
import { Kanban, TypeState } from '@anticrm/kanban'
|
||||
import notification from '@anticrm/notification'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import tags from '@anticrm/tags'
|
||||
import { Issue, IssuesGrouping, IssuesOrdering, IssueStatus, Team } from '@anticrm/tracker'
|
||||
import { Button, Component, IconAdd, showPanel, showPopup, Loading, tooltip } from '@anticrm/ui'
|
||||
import {
|
||||
Button,
|
||||
Component,
|
||||
getEventPositionElement,
|
||||
IconAdd,
|
||||
Loading,
|
||||
showPanel,
|
||||
showPopup,
|
||||
tooltip
|
||||
} from '@anticrm/ui'
|
||||
import { focusStore, ListSelectionProvider, SelectDirection, selectionStore } from '@anticrm/view-resources'
|
||||
import ActionContext from '@anticrm/view-resources/src/components/ActionContext.svelte'
|
||||
import Menu from '@anticrm/view-resources/src/components/Menu.svelte'
|
||||
@ -37,11 +47,10 @@
|
||||
import AssigneePresenter from './AssigneePresenter.svelte'
|
||||
import SubIssuesSelector from './edit/SubIssuesSelector.svelte'
|
||||
import IssuePresenter from './IssuePresenter.svelte'
|
||||
import IssueStatusIcon from './IssueStatusIcon.svelte'
|
||||
import ParentNamesPresenter from './ParentNamesPresenter.svelte'
|
||||
import PriorityEditor from './PriorityEditor.svelte'
|
||||
import StatusEditor from './StatusEditor.svelte'
|
||||
import tags from '@anticrm/tags'
|
||||
import IssueStatusIcon from './IssueStatusIcon.svelte'
|
||||
|
||||
export let currentSpace: Ref<Team> = tracker.team.DefaultTeam
|
||||
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
|
||||
@ -106,16 +115,9 @@
|
||||
|
||||
const showMenu = async (ev: MouseEvent, items: Doc[]): Promise<void> => {
|
||||
ev.preventDefault()
|
||||
showPopup(
|
||||
Menu,
|
||||
{ object: items, baseMenuClass },
|
||||
{
|
||||
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY })
|
||||
},
|
||||
() => {
|
||||
showPopup(Menu, { object: items, baseMenuClass }, getEventPositionElement(ev), () => {
|
||||
// selection = undefined
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
const issuesQuery = createQuery()
|
||||
let issueStates: TypeState[] = []
|
||||
|
@ -13,25 +13,25 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { flip } from 'svelte/animate'
|
||||
import { Doc, WithLookup } from '@anticrm/core'
|
||||
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
||||
import { getEventPositionElement, showPanel, showPopup } from '@anticrm/ui'
|
||||
import {
|
||||
ActionContext,
|
||||
ContextMenu,
|
||||
FixedColumn,
|
||||
ListSelectionProvider,
|
||||
SelectDirection,
|
||||
FixedColumn
|
||||
SelectDirection
|
||||
} from '@anticrm/view-resources'
|
||||
import { showPanel, showPopup } from '@anticrm/ui'
|
||||
import tracker from '../../../plugin'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { flip } from 'svelte/animate'
|
||||
import { getIssueId } from '../../../issues'
|
||||
import tracker from '../../../plugin'
|
||||
import Circles from '../../icons/Circles.svelte'
|
||||
import AssigneeEditor from '../AssigneeEditor.svelte'
|
||||
import DueDateEditor from '../DueDateEditor.svelte'
|
||||
import StatusEditor from '../StatusEditor.svelte'
|
||||
import PriorityEditor from '../PriorityEditor.svelte'
|
||||
import StatusEditor from '../StatusEditor.svelte'
|
||||
|
||||
export let issues: Issue[]
|
||||
export let issueStatuses: WithLookup<IssueStatus>[]
|
||||
@ -70,11 +70,7 @@
|
||||
}
|
||||
|
||||
function showContextMenu (ev: MouseEvent, object: Issue) {
|
||||
showPopup(
|
||||
ContextMenu,
|
||||
{ object },
|
||||
{ getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY }) }
|
||||
)
|
||||
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
|
||||
}
|
||||
|
||||
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
|
||||
|
@ -120,7 +120,13 @@ export function filterActions (
|
||||
const role = getCurrentAccount().role
|
||||
const clazz = hierarchy.getClass(doc._class)
|
||||
const ignoreActions = hierarchy.as(clazz, view.mixin.IgnoreActions)
|
||||
const ignore = ignoreActions?.actions ?? []
|
||||
const ignore: Array<Ref<Action>> = ignoreActions?.actions ?? []
|
||||
|
||||
// Collect ignores from parent
|
||||
hierarchy.getAncestors(clazz._id).forEach((cl) => {
|
||||
const ignoreActions = hierarchy.as(hierarchy.getClass(cl), view.mixin.IgnoreActions)
|
||||
ignore.push(...(ignoreActions?.actions ?? []))
|
||||
})
|
||||
const overrideRemove: Array<Ref<Action>> = []
|
||||
for (const action of actions) {
|
||||
if (ignore.includes(action._id)) {
|
||||
|
@ -30,12 +30,12 @@
|
||||
let keys: KeyedAttribute[] = []
|
||||
let collapsed: boolean = false
|
||||
|
||||
function updateKeys (ignoreKeys: string[]): void {
|
||||
function updateKeys (_class: Ref<Class<Doc>>, ignoreKeys: string[], to: Ref<Class<Doc>> | undefined): void {
|
||||
const filtredKeys = getFiltredKeys(hierarchy, _class, ignoreKeys, to)
|
||||
keys = filtredKeys.filter((key) => !isCollectionAttr(hierarchy, key) || allowedCollections.includes(key.key))
|
||||
}
|
||||
|
||||
$: updateKeys(ignoreKeys)
|
||||
$: updateKeys(_class, ignoreKeys, to)
|
||||
|
||||
$: label = hierarchy.getClass(_class).label
|
||||
</script>
|
||||
|
@ -14,15 +14,21 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Doc, Mixin } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import setting from '@anticrm/setting'
|
||||
import ClassAttributeBar from './ClassAttributeBar.svelte'
|
||||
|
||||
export let object: Doc
|
||||
export let mixins: Mixin<Doc>[]
|
||||
export let ignoreKeys: string[]
|
||||
export let allowedCollections: string[] = []
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
</script>
|
||||
|
||||
<ClassAttributeBar _class={object._class} {object} {ignoreKeys} to={undefined} {allowedCollections} on:update />
|
||||
{#each mixins as mixin}
|
||||
<ClassAttributeBar _class={mixin._id} {object} {ignoreKeys} to={object._class} {allowedCollections} on:update />
|
||||
{@const to = !hierarchy.hasMixin(mixin, setting.mixin.UserMixin) ? object._class : mixin.extends}
|
||||
<ClassAttributeBar _class={mixin._id} {object} {ignoreKeys} {to} {allowedCollections} on:update />
|
||||
{/each}
|
||||
|
@ -26,7 +26,7 @@
|
||||
getClient,
|
||||
KeyedAttribute
|
||||
} from '@anticrm/presentation'
|
||||
import { AnyComponent, Component } from '@anticrm/ui'
|
||||
import { AnyComponent, Button, Component } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { collectionsFilter, getCollectionCounter, getFiltredKeys } from '../utils'
|
||||
@ -77,17 +77,22 @@
|
||||
|
||||
let mixins: Mixin<Doc>[] = []
|
||||
|
||||
let showAllMixins = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function getMixins (parentClass: Ref<Class<Doc>>, object: Doc): void {
|
||||
function getMixins (parentClass: Ref<Class<Doc>>, object: Doc, showAllMixins: boolean): void {
|
||||
if (object === undefined || parentClass === undefined) return
|
||||
const descendants = hierarchy.getDescendants(parentClass).map((p) => hierarchy.getClass(p))
|
||||
mixins = descendants.filter(
|
||||
(m) => m.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(object, m._id) && !ignoreMixins.has(m._id)
|
||||
(m) =>
|
||||
m.kind === ClassifierKind.MIXIN &&
|
||||
!ignoreMixins.has(m._id) &&
|
||||
(hierarchy.hasMixin(object, m._id) || showAllMixins)
|
||||
)
|
||||
}
|
||||
|
||||
$: getMixins(parentClass, object)
|
||||
$: getMixins(parentClass, object, showAllMixins)
|
||||
|
||||
let ignoreKeys: string[] = []
|
||||
let allowedCollections: string[] = []
|
||||
@ -239,6 +244,30 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="attributes" let:direction={dir}>
|
||||
<div class="flex flex-reverse flex-grow">
|
||||
<Button
|
||||
kind={'transparent'}
|
||||
shape={'round'}
|
||||
selected={showAllMixins}
|
||||
on:click={() => {
|
||||
showAllMixins = !showAllMixins
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.66602" y="2.66663" width="10.6667" height="4.66667" rx="1" stroke="white" />
|
||||
<path
|
||||
d="M2.66602 11.3334C2.66602 10.3906 2.66602 9.91916 2.95891 9.62627C3.2518 9.33337 3.72321 9.33337 4.66602 9.33337H6.66602V11.3334C6.66602 12.2762 6.66602 12.7476 6.37312 13.0405C6.37312 13.0405 6.37312 13.0405 6.37312 13.0405C6.08023 13.3334 5.60882 13.3334 4.66602 13.3334V13.3334C3.72321 13.3334 3.2518 13.3334 2.95891 13.0405C2.95891 13.0405 2.95891 13.0405 2.95891 13.0405C2.66602 12.7476 2.66602 12.2762 2.66602 11.3334V11.3334Z"
|
||||
stroke="white"
|
||||
/>
|
||||
<path
|
||||
d="M9.33398 9.33337H11.334C12.2768 9.33337 12.7482 9.33337 13.0411 9.62627C13.334 9.91916 13.334 10.3906 13.334 11.3334V11.3334C13.334 12.2762 13.334 12.7476 13.0411 13.0405C12.7482 13.3334 12.2768 13.3334 11.334 13.3334V13.3334C10.3912 13.3334 9.91977 13.3334 9.62688 13.0405C9.33398 12.7476 9.33398 12.2762 9.33398 11.3334V9.33337Z"
|
||||
stroke="white"
|
||||
/>
|
||||
</svg>
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
</div>
|
||||
{#if !headerLoading}
|
||||
{#if headerEditor !== undefined}
|
||||
<Component
|
||||
@ -269,7 +298,7 @@
|
||||
ignoreMixins = new Set(ev.detail.ignoreMixins)
|
||||
allowedCollections = ev.detail.allowedCollections ?? []
|
||||
collectionArrays = ev.detail.collectionArrays ?? []
|
||||
getMixins(parentClass, object)
|
||||
getMixins(parentClass, object, showAllMixins)
|
||||
updateKeys()
|
||||
}}
|
||||
/>
|
||||
|
@ -18,7 +18,17 @@
|
||||
import { getObjectValue, SortingOrder } from '@anticrm/core'
|
||||
import notification from '@anticrm/notification'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { CheckBox, Component, IconDown, IconUp, Label, Loading, showPopup, Spinner } from '@anticrm/ui'
|
||||
import {
|
||||
CheckBox,
|
||||
Component,
|
||||
getEventPositionElement,
|
||||
IconDown,
|
||||
IconUp,
|
||||
Label,
|
||||
Loading,
|
||||
showPopup,
|
||||
Spinner
|
||||
} from '@anticrm/ui'
|
||||
import { BuildModelKey } from '@anticrm/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { buildConfigLookup, buildModel, LoadingProps } from '../utils'
|
||||
@ -103,16 +113,9 @@
|
||||
checked = []
|
||||
}
|
||||
const items = checked.length > 0 ? checked : object
|
||||
showPopup(
|
||||
Menu,
|
||||
{ object: items, baseMenuClass },
|
||||
{
|
||||
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY })
|
||||
},
|
||||
() => {
|
||||
showPopup(Menu, { object: items, baseMenuClass }, getEventPositionElement(ev), () => {
|
||||
selection = undefined
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function changeSorting (key: string): void {
|
||||
|
Loading…
Reference in New Issue
Block a user