Skip to content

Commit

Permalink
feat: introduce new categories field for UI Action Buttons (#33066)
Browse files Browse the repository at this point in the history
Co-authored-by: Tasso Evangelista <[email protected]>
  • Loading branch information
Dnouv and tassoevan authored Oct 10, 2024
1 parent 760ae5c commit debd3ff
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 7 deletions.
7 changes: 7 additions & 0 deletions .changeset/red-crews-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/ui-kit': minor
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Introduces new property `category` for Rocket.Chat Apps to register UI action buttons. This property is used to group buttons in the UI.
16 changes: 16 additions & 0 deletions apps/meteor/client/components/message/toolbar/MessageToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoT
import { useChat } from '../../../views/room/contexts/ChatContext';
import { useRoomToolbox } from '../../../views/room/contexts/RoomToolboxContext';
import MessageActionMenu from './MessageActionMenu';
import MessageToolbarStarsActionMenu from './MessageToolbarStarsActionMenu';
import { useWebDAVMessageAction } from './useWebDAVMessageAction';

const getMessageContext = (message: IMessage, room: IRoom, context?: MessageActionContext): MessageActionContext => {
Expand Down Expand Up @@ -78,6 +79,8 @@ const MessageToolbar = ({

const actionButtonApps = useMessageActionAppsActionButtons(context);

const starsAction = useMessageActionAppsActionButtons(context, 'ai');

const { messageToolbox: hiddenActions } = useLayoutHiddenActions();

// TODO: move this to another place
Expand Down Expand Up @@ -135,6 +138,19 @@ const MessageToolbar = ({
disabled={action?.disabled?.({ message, room, user, subscription, settings: mapSettings, chat, context })}
/>
))}
{starsAction.data && starsAction.data.length > 0 && (
<MessageToolbarStarsActionMenu
options={starsAction.data.map((action) => ({
...action,
action: (e) => action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions }),
}))}
onChangeMenuVisibility={onChangeMenuVisibility}
data-qa-type='message-action-stars-menu-options'
context={{ message, room, user, subscription, settings: mapSettings, chat, context }}
isMessageEncrypted={isE2EEMessage(message)}
/>
)}

{actionsQueryResult.isSuccess && actionsQueryResult.data.menu.length > 0 && (
<MessageActionMenu
options={[...actionsQueryResult.data?.menu, ...(actionButtonApps.data ?? [])].filter(Boolean).map((action) => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { GenericMenu, type GenericMenuItemProps } from '@rocket.chat/ui-client';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { MouseEvent, ReactElement } from 'react';
import React from 'react';

import type { MessageActionConditionProps, MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction';

type MessageActionConfigOption = Omit<MessageActionConfig, 'condition' | 'context' | 'order' | 'action'> & {
action: (e?: MouseEvent<HTMLElement>) => void;
};

type MessageActionSection = {
id: string;
title: string;
items: GenericMenuItemProps[];
};

type MessageActionMenuProps = {
onChangeMenuVisibility: (visible: boolean) => void;
options: MessageActionConfigOption[];
context: MessageActionConditionProps;
isMessageEncrypted: boolean;
};

const MessageToolbarStarsActionMenu = ({
options,
onChangeMenuVisibility,
context,
isMessageEncrypted,
}: MessageActionMenuProps): ReactElement => {
const t = useTranslation();
const id = useUniqueId();

const groupOptions = options.reduce((acc, option) => {
const transformedOption = {
variant: option.color === 'alert' ? 'danger' : '',
id: option.id,
icon: option.icon,
content: t(option.label),
onClick: option.action,
type: option.type,
...(option.disabled && { disabled: option?.disabled?.(context) }),
...(option.disabled &&
option?.disabled?.(context) && { tooltip: t('Action_not_available_encrypted_content', { action: t(option.label) }) }),
};

const group = option.type || '';
let section = acc.find((section: { id: string }) => section.id === group);

if (!section) {
section = { id: group, title: '', items: [] };
acc.push(section);
}

// Add option to the appropriate section
section.items.push(transformedOption);

// Handle the "apps" section if message is encrypted
if (group === 'apps' && isMessageEncrypted) {
section.items = [
{
content: t('Unavailable'),
id,
disabled: true,
gap: false,
tooltip: t('Action_not_available_encrypted_content', { action: t('Apps') }),
},
];
}

return acc;
}, [] as MessageActionSection[]);

return (
<GenericMenu
onOpenChange={onChangeMenuVisibility}
detached
icon='stars'
title={t('AI_Actions')}
data-qa-id='menu'
data-qa-type='message-action-menu'
sections={groupOptions}
placement='bottom-end'
/>
);
};

export default MessageToolbarStarsActionMenu;
13 changes: 6 additions & 7 deletions apps/meteor/client/hooks/useAppActionButtons.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IUIActionButton, UIActionButtonContext } from '@rocket.chat/apps-engine/definition/ui';
import { type IUIActionButton, type UIActionButtonContext } from '@rocket.chat/apps-engine/definition/ui';
import { useDebouncedCallback } from '@rocket.chat/fuselage-hooks';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { useEndpoint, useStream, useToastMessageDispatch, useUserId } from '@rocket.chat/ui-contexts';
Expand All @@ -13,6 +13,7 @@ import type { MessageBoxAction } from '../../app/ui-utils/client/lib/messageBox'
import { Utilities } from '../../ee/lib/misc/Utilities';
import { useUiKitActionManager } from '../uikit/hooks/useUiKitActionManager';
import { useApplyButtonFilters, useApplyButtonAuthFilter } from './useApplyButtonFilters';
import { useFilterActionsByContextAndCategory } from './useFilterActions';

const getIdForActionButton = ({ appId, actionId }: IUIActionButton): string => `${appId}/${actionId}`;

Expand Down Expand Up @@ -160,20 +161,18 @@ export const useUserDropdownAppsActionButtons = () => {
} as UseQueryResult<GenericMenuItemProps[]>;
};

export const useMessageActionAppsActionButtons = (context?: MessageActionContext) => {
export const useMessageActionAppsActionButtons = (context?: MessageActionContext, category?: string) => {
const result = useAppActionButtons('messageAction');
const actionManager = useUiKitActionManager();
const applyButtonFilters = useApplyButtonFilters();
const dispatchToastMessage = useToastMessageDispatch();
const { t } = useTranslation();
const filterActionsByContextAndCategory = useFilterActionsByContextAndCategory(context, category);
const data = useMemo(
() =>
result.data
?.filter((action) => {
if (
context &&
!(action.when?.messageActionContext || ['message', 'message-mobile', 'threads', 'starred']).includes(context as any)
) {
if (!filterActionsByContextAndCategory(action)) {
return false;
}
return applyButtonFilters(action);
Expand Down Expand Up @@ -212,7 +211,7 @@ export const useMessageActionAppsActionButtons = (context?: MessageActionContext

return item;
}),
[actionManager, applyButtonFilters, context, dispatchToastMessage, result.data, t],
[actionManager, applyButtonFilters, dispatchToastMessage, filterActionsByContextAndCategory, result.data, t],
);
return {
...result,
Expand Down
24 changes: 24 additions & 0 deletions apps/meteor/client/hooks/useFilterActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { MessageActionContext } from '@rocket.chat/apps-engine/definition/ui';
import type { IUIActionButton } from '@rocket.chat/apps-engine/definition/ui';
import { useCallback } from 'react';

const DEFAULT_CATEGORY = 'default';

export const useFilterActionsByContextAndCategory = (context: string | undefined, category = 'default') => {
return useCallback(
(action: IUIActionButton) => {
if (!context) {
return true;
}

const actionCategory = action?.category ?? DEFAULT_CATEGORY;
const messageActionContext = action.when?.messageActionContext || Object.values(MessageActionContext);
const isContextMatch = messageActionContext.includes(context as MessageActionContext);

const isCategoryMatch = category === DEFAULT_CATEGORY ? actionCategory === DEFAULT_CATEGORY : actionCategory === category;

return isContextMatch && isCategoryMatch;
},
[context, category],
);
};
1 change: 1 addition & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@
"Agent_Without_Extensions": "Agent Without Extensions",
"Agents": "Agents",
"Agree": "Agree",
"AI_Actions": "AI Actions",
"Alerts": "Alerts",
"Alias": "Alias",
"Alias_Format": "Alias Format",
Expand Down

0 comments on commit debd3ff

Please sign in to comment.