// // Copyright © 2020, 2021 Anticrm Platform Contributors. // 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. // import core, { AccountRole, Doc, getCurrentAccount, WithLookup, Class, Client, matchQuery, Ref } from '@hcengineering/core' import { getResource } from '@hcengineering/platform' import type { Action, ViewAction, ViewActionInput, ViewContextType } from '@hcengineering/view' import view from './plugin' import { FocusSelection } from './selection' /** * @public */ export function getSelection (focusStore: FocusSelection, selectionStore: Doc[]): Doc[] { let docs: Doc[] = [] if (selectionStore.length > 0) { docs = selectionStore } else if (focusStore.focus !== undefined) { docs = [focusStore.focus] } return docs } /** * @public * * Find all action contributions applicable for specified _class. * If derivedFrom is specified, only actions applicable to derivedFrom class will be used. * So if we have contribution for Doc, Space and we ask for SpaceWithStates and derivedFrom=Space, * we won't receive Doc contribution but receive Space ones. */ export async function getActions ( client: Client, doc: Doc | Doc[], derived: Ref> = core.class.Doc, mode: ViewContextType = 'context' ): Promise { const actions: Action[] = await client.findAll(view.class.Action, { 'context.mode': mode }) const categories: Record = { top: 1, filter: 50, tools: 100 } let filteredActions = [] for (const action of actions) { if (action.visibilityTester == null) { filteredActions.push(action) } else { const visibilityTester = await getResource(action.visibilityTester) if (await visibilityTester(doc)) { filteredActions.push(action) } } } if (Array.isArray(doc)) { for (const d of doc) { filteredActions = filterActions(client, d, filteredActions, derived) } } else { filteredActions = filterActions(client, doc, filteredActions, derived) } const inputVal: ViewActionInput[] = ['none'] if (!Array.isArray(doc) || doc.length === 1) { inputVal.push('focus') inputVal.push('any') } if (Array.isArray(doc) && doc.length > 0) { inputVal.push('selection') inputVal.push('any') } filteredActions = filteredActions.filter((it) => inputVal.includes(it.input)) filteredActions.sort((a, b) => { const aTarget = categories[a.context.group ?? 'top'] ?? 0 const bTarget = categories[b.context.group ?? 'top'] ?? 0 return aTarget - bTarget }) return filteredActions } export async function invokeAction ( object: Doc | Doc[], evt: Event, action: ViewAction, props?: Record ): Promise { const impl = await getResource(action) await impl(Array.isArray(object) && object.length === 1 ? object[0] : object, evt, props) } export async function getContextActions ( client: Client, doc: Doc | Doc[], context: { mode: ViewContextType application?: Ref } ): Promise { const result = await getActions(client, doc, undefined, context.mode) if (context.application !== undefined) { return result.filter((it) => it.context.application === context.application || it.context.application === undefined) } return result } /** * @public */ export function filterActions ( client: Client, doc: Doc, actions: Array>, derived: Ref> = core.class.Doc ): Array> { let result: Array> = [] const hierarchy = client.getHierarchy() const role = getCurrentAccount().role const clazz = hierarchy.getClass(doc._class) const ignoreActions = hierarchy.as(clazz, view.mixin.IgnoreActions) const ignore: Array> = Array.from(ignoreActions?.actions ?? []) // Collect ignores from parent const ancestors = hierarchy.getAncestors(clazz._id) for (const cl of ancestors) { const ignoreActions = hierarchy.as(hierarchy.getClassOrInterface(cl), view.mixin.IgnoreActions) if (ignoreActions?.actions !== undefined) { ignore.push(...ignoreActions.actions) } } const overrideRemove: Array> = [] for (const action of actions) { if (ignore.includes(action._id)) { continue } if (role < AccountRole.Maintainer && action.secured === true) { continue } if (action.query !== undefined) { const r = matchQuery([doc], action.query, doc._class, hierarchy) if (r.length === 0) { continue } } if ( (hierarchy.isDerived(doc._class, action.target) && client.getHierarchy().isDerived(action.target, derived)) || (hierarchy.isMixin(action.target) && hierarchy.hasMixin(doc, action.target)) ) { if (action.override !== undefined) { overrideRemove.push(...action.override) } result.push(action) } } if (overrideRemove.length > 0) { result = result.filter((it) => !overrideRemove.includes(it._id)) } return result }