import { Class, Doc, DocumentQuery, Hierarchy, Ref } from '@anticrm/core' import { Asset, getResource, IntlString, Resource } from '@anticrm/platform' import { getClient, MessageBox, updateAttribute } from '@anticrm/presentation' import { AnyComponent, AnySvelteComponent, closeTooltip, isPopupPosAlignment, PopupAlignment, PopupPosAlignment, showPanel, showPopup } from '@anticrm/ui' import { Action, ViewContext } from '@anticrm/view' import MoveView from './components/Move.svelte' import { contextStore } from './context' import view from './plugin' import { FocusSelection, focusStore, previewDocument, SelectDirection, selectionStore } from './selection' import { deleteObject } from './utils' function Delete (object: Doc): void { showPopup( MessageBox, { label: view.string.DeleteObject, message: view.string.DeleteObjectConfirm, params: { count: Array.isArray(object) ? object.length : 1 } }, 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)) } } } ) } async function Move (object: Doc): Promise { showPopup(MoveView, { object }) } let $focusStore: FocusSelection focusStore.subscribe((it) => { $focusStore = it }) let $contextStore: ViewContext[] contextStore.subscribe((it) => { $contextStore = it }) export function select (evt: Event | undefined, offset: 1 | -1 | 0, of?: Doc, direction?: SelectDirection): void { closeTooltip() if ($focusStore.provider?.select !== undefined) { $focusStore.provider?.select(offset, of, direction) evt?.preventDefault() previewDocument.update((old) => { if (old !== undefined) { return $focusStore.focus } }) } } function SelectItem (doc: Doc | undefined, evt: Event): void { if (doc !== undefined) { selectionStore.update((selection) => { const ind = selection.findIndex((it) => it._id === doc._id) if (ind === -1) { selection.push(doc) } else { selection.splice(ind, 1) } return selection }) } evt.preventDefault() } function SelectItemNone (doc: Doc | undefined, evt: Event): void { selectionStore.set([]) previewDocument.set(undefined) evt.preventDefault() } function SelectItemAll (doc: Doc | undefined, evt: Event): void { const docs = $focusStore.provider?.docs() ?? [] selectionStore.set(docs) previewDocument.set(undefined) evt.preventDefault() } const MoveUp = (doc: Doc | undefined, evt: Event): void => select(evt, -1, doc, 'vertical') const MoveDown = (doc: Doc | undefined, evt: Event): void => select(evt, 1, doc, 'vertical') const MoveLeft = (doc: Doc | undefined, evt: Event): void => select(evt, -1, doc, 'horizontal') const MoveRight = (doc: Doc | undefined, evt: Event): void => select(evt, 1, doc, 'horizontal') function ShowActions (doc: Doc | Doc[] | undefined, evt: Event): void { evt.preventDefault() showPopup(view.component.ActionsPopup, { viewContext: $contextStore[$contextStore.length - 1] }, 'top') } function ShowPreview (doc: Doc | undefined, evt: Event): void { previewDocument.update((old) => { if (old?._id === doc?._id) { return undefined } return doc }) evt.preventDefault() } function Open (doc: Doc, evt: Event): void { evt.preventDefault() showPanel(view.component.EditDoc, doc._id, Hierarchy.mixinOrClass(doc), 'content') } /** * Quick action for show panel * Require props: * - component - view.component.EditDoc or another component * - element - position * - right - some right component */ function ShowPanel ( doc: Doc | Doc[], evt: Event, props: { component?: AnyComponent element: PopupPosAlignment rightSection?: AnyComponent } ): void { if (Array.isArray(doc)) { console.error('Wrong show Panel parameters') return } evt.preventDefault() showPanel( props.component ?? view.component.EditDoc, doc._id, Hierarchy.mixinOrClass(doc), props.element ?? 'content', props.rightSection ) } /** * Quick action for show popup * Props: * - _id - object id will be placed into * - _class - object _class will be placed into * - value - object itself will be placed into * - values - all docs will be placed into * - props - some basic props, will be merged with key, _class, value, values */ async function ShowPopup ( doc: Doc | Doc[], evt: Event, props: { component: AnyComponent element?: PopupPosAlignment | Resource<(e?: Event) => PopupAlignment | undefined> _id?: string _class?: string _space?: string value?: string values?: string props?: Record fillProps?: Record } ): Promise { const docs = Array.isArray(doc) ? doc : doc !== undefined ? [doc] : [] const element = await getPopupAlignment(props.element, evt) evt.preventDefault() let cprops = { ...(props?.props ?? {}) } for (const [docKey, propKey] of Object.entries(props.fillProps ?? {})) { for (const dv of docs) { const dvv = (dv as any)[docKey] if (dvv !== undefined) { ;(cprops as any)[propKey] = { dvv } } } if (docKey === '_object') { ;(cprops as any)[propKey] = docs[0] } } if (docs.length > 0) { cprops = { ...cprops, ...{ [props._id ?? '_id']: docs[0]._id, [props._class ?? '_class']: docs[0]._class, [props._space ?? 'space']: docs[0].space, [props.value ?? 'value']: docs[0], [props.values ?? 'values']: docs } } } showPopup(props.component, cprops, element) } /** * Quick action for show popup * Props: * - attribute - to show editor for specific attribute * - props - some basic props, will be merged with key, _class, value, values */ async function ShowEditor ( doc: Doc | Doc[], evt: Event, props: { element?: PopupPosAlignment | Resource<(e?: Event) => PopupAlignment | undefined> attribute: string props?: Record } ): Promise { const docs = Array.isArray(doc) ? doc : doc !== undefined ? [doc] : [] evt.preventDefault() let cprops = { ...(props?.props ?? {}) } if (docs.length === 1) { const client = getClient() const hierarchy = client.getHierarchy() const doc: Doc = docs[0] const attribute = hierarchy.getAttribute(doc._class, props.attribute) const typeClass = hierarchy.getClass(attribute.type._class) const attributeEditorMixin = hierarchy.as(typeClass, view.mixin.AttributeEditor) if (attributeEditorMixin === undefined || attributeEditorMixin.popup === undefined) { throw new Error(`failed to find editor popup for ${typeClass._id}`) } const editor: AnySvelteComponent = await getResource(attributeEditorMixin.popup) cprops = { ...cprops, ...{ value: (doc as any)[props.attribute] } } if (editor !== undefined) { console.log('EVT', evt) showPopup( editor, cprops, { getBoundingClientRect: () => new DOMRect((evt as MouseEvent).clientX, (evt as MouseEvent).clientY) }, (result) => { if (result != null) { void updateAttribute(client, doc, doc._class, { key: props.attribute, attr: attribute }, result) } } ) } } } function UpdateDocument (doc: Doc | Doc[], evt: Event, props: Record): void { async function update (): Promise { if (props?.key !== undefined && props?.value !== undefined) { if (Array.isArray(doc)) { for (const d of doc) { await getClient().update(d, { [props.key]: props.value }) } } else { await getClient().update(doc, { [props.key]: props.value }) } } } if (props?.ask === true) { showPopup( MessageBox, { label: props.label ?? view.string.LabelYes, message: props.message ?? view.string.LabelYes }, undefined, (result: boolean) => { if (result) { void update() } } ) } else { void update() } } function ValueSelector ( doc: Doc | Doc[], evt: Event, props: { action: Action attribute: string // Class object finder _class?: Ref> query?: DocumentQuery // Will copy values from selection document to query // If set of docs passed, will do $in for values. fillQuery?: Record // A list of fields with matched values to perform action. docMatches?: string[] searchField?: string // Or list of values to select from values?: Array<{ icon?: Asset, label: IntlString, id: number | string }> placeholder?: IntlString } ): void { if (props.action.actionPopup !== undefined) { showPopup(props.action.actionPopup, { ...props, ...props.action.actionProps, value: doc, width: 'large' }, 'top') } } async function getPopupAlignment ( element?: PopupPosAlignment | Resource<(e?: Event) => PopupAlignment | undefined>, evt?: Event ): Promise { if (element === undefined) { return undefined } if (isPopupPosAlignment(element)) { return element } try { const alignmentGetter: (e?: Event) => PopupAlignment | undefined = await getResource(element) return alignmentGetter(evt) } catch (e) { return element as PopupAlignment } } /** * @public */ export const actionImpl = { Delete, Move, MoveUp, MoveDown, MoveLeft, MoveRight, SelectItem, SelectItemNone, SelectItemAll, ShowActions, ShowPreview, Open, UpdateDocument, ShowPanel, ShowPopup, ShowEditor, ValueSelector }