From 341c34a97deb1e2f4613d9f6146383662e673c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pete=20An=C3=B8ther?= <develop.pit@gmail.com> Date: Tue, 23 Jan 2024 14:08:29 -0300 Subject: [PATCH] EZQMS-527: Introduced `ActionButton` component (#4412) EZQMS-527: Introduced `ActionButton` component --------- Signed-off-by: Petr Vyazovetskiy <develop.pit@gmail.com> --- plugins/view-resources/src/actions.ts | 68 +++++++++--------- .../src/components/ActionButton.svelte | 71 +++++++++++++++++++ plugins/view-resources/src/index.ts | 1 + 3 files changed, 108 insertions(+), 32 deletions(-) create mode 100644 plugins/view-resources/src/components/ActionButton.svelte diff --git a/plugins/view-resources/src/actions.ts b/plugins/view-resources/src/actions.ts index f9551619f6..ce7fa036de 100644 --- a/plugins/view-resources/src/actions.ts +++ b/plugins/view-resources/src/actions.ts @@ -64,42 +64,13 @@ export async function getActions ( derived: Ref<Class<Doc>> = core.class.Doc, mode: ViewContextType = 'context' ): Promise<Action[]> { - let actions: Action[] = await client.findAll(view.class.Action, { + const actions: Action[] = await client.findAll(view.class.Action, { 'context.mode': mode }) + const filteredActions = await filterAvailableActions(actions, client, doc, derived) + const categories: Partial<Record<ActionGroup | 'top', number>> = { top: 1, tools: 50, other: 100, remove: 200 } - - if (Array.isArray(doc)) { - for (const d of doc) { - actions = filterActions(client, d, actions, derived) - } - } else { - actions = filterActions(client, doc, actions, 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') - } - actions = actions.filter((it) => inputVal.includes(it.input)) - - const filteredActions: Action[] = [] - 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) - } - } - } filteredActions.sort((a, b) => { const aTarget = categories[a.context.group ?? 'top'] ?? 0 const bTarget = categories[b.context.group ?? 'top'] ?? 0 @@ -108,6 +79,39 @@ export async function getActions ( return filteredActions } +export async function filterAvailableActions ( + actions: Action[], + client: Client, + doc: Doc | Doc[], + derived: Ref<Class<Doc>> = core.class.Doc +): Promise<Action[]> { + actions = (Array.isArray(doc) ? doc : [doc]).reduce( + (actions, doc) => filterActions(client, doc, actions, derived), + actions + ) + + const input = (['none'] as ViewActionInput[]) + .concat(Array.isArray(doc) && doc.length > 0 ? ['selection', 'any'] : []) + .concat(!Array.isArray(doc) || doc.length === 1 ? ['focus', 'any'] : []) + actions = actions.filter((it) => input.includes(it.input)) + + const result: Action[] = [] + for (const action of actions) { + if (action.visibilityTester == null) { + result.push(action) + } else { + const visibilityTester = await getResource(action.visibilityTester) + + if (await visibilityTester(doc)) { + result.push(action) + } + } + } + + return result +} + +/** @public */ export async function invokeAction ( object: Doc | Doc[], evt: Event, diff --git a/plugins/view-resources/src/components/ActionButton.svelte b/plugins/view-resources/src/components/ActionButton.svelte new file mode 100644 index 0000000000..0ae9cf0eea --- /dev/null +++ b/plugins/view-resources/src/components/ActionButton.svelte @@ -0,0 +1,71 @@ +<!-- +// 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 { ComponentProps } from 'svelte' + import { Button } from '@hcengineering/ui' + import { Doc, Ref } from '@hcengineering/core' + import { Action, ViewContextType } from '@hcengineering/view' + import { getClient } from '@hcengineering/presentation' + import { filterAvailableActions, invokeAction } from '../actions' + import view from '../plugin' + + type $$Props = Omit<ComponentProps<Button>, 'icon' | 'label'> & { + id: Ref<Action> + object: Doc | Doc[] + mode?: ViewContextType + } + export let disabled: boolean = false + export let id: Ref<Action> + export let object: Doc | Doc[] + export let mode: ViewContextType | undefined = undefined + + const client = getClient() + + let action: Action | null = null + $: void client + .findOne(view.class.Action, { + 'context.mode': mode, + _id: id + }) + .then((result) => { + action = result ?? null + }) + + let isAvailable = false + $: if (action !== null) { + void filterAvailableActions([action], client, object).then((result) => { + isAvailable = result[0] === action + }) + } + + let isBeingInvoked = false +</script> + +{#if action !== null && isAvailable} + <Button + {...$$props} + icon={action.icon} + label={action.label} + disabled={disabled || isBeingInvoked} + on:click={async (event) => { + if (action !== null) { + isBeingInvoked = true + await invokeAction(object, event, action.action, action.actionProps) + isBeingInvoked = false + } + }} + /> +{/if} diff --git a/plugins/view-resources/src/index.ts b/plugins/view-resources/src/index.ts index 46ef6328fb..4543fd9df8 100644 --- a/plugins/view-resources/src/index.ts +++ b/plugins/view-resources/src/index.ts @@ -114,6 +114,7 @@ import { IndexedDocumentPreview } from '@hcengineering/presentation' import { showEmptyGroups } from './viewOptions' import { AggregationMiddleware } from './middleware' export { getActions, invokeAction, getContextActions } from './actions' +export { default as ActionButton } from './components/ActionButton.svelte' export { default as ActionHandler } from './components/ActionHandler.svelte' export { default as FilterButton } from './components/filter/FilterButton.svelte' export { default as FixedColumn } from './components/FixedColumn.svelte'