Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce new categories field for UI Action Buttons #33066

Merged
merged 31 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3657d89
init add new hook
Dnouv Aug 15, 2024
01fd0c6
add message action menu
Dnouv Aug 15, 2024
6bc0a97
Add AI Actions to en.i18n.json
Dnouv Aug 15, 2024
822c21c
add changeset
Dnouv Aug 15, 2024
5f54fbf
update apps engine
Dnouv Aug 20, 2024
74553f2
Merge branch 'develop' into new/app_ui_menu
Dnouv Aug 21, 2024
7a16d55
update rc-icons
Dnouv Aug 21, 2024
f99b4cd
Revert "update rc-icons"
Dnouv Aug 21, 2024
659e3df
Merge branch 'develop' into new/app_ui_menu
Dnouv Aug 21, 2024
26ace4c
Merge branch 'develop' into new/app_ui_menu
Dnouv Aug 22, 2024
b126016
Add blank space in en.i18n.json
Dnouv Aug 23, 2024
9cdf49a
Merge branch 'develop' into new/app_ui_menu
Dnouv Aug 23, 2024
2ee01f8
Merge branch 'develop' into new/app_ui_menu
Dnouv Sep 9, 2024
8b2fbf1
Merge branch 'develop' into new/app_ui_menu
Dnouv Sep 9, 2024
7e60300
update to use category
Dnouv Sep 18, 2024
81b0daa
optimize bool checks
Dnouv Sep 18, 2024
06dc501
update changeset
Dnouv Sep 18, 2024
c7f44bb
Merge branch 'develop' into new/app_ui_menu
Dnouv Sep 26, 2024
290f5ed
bump apps-engine
Dnouv Sep 26, 2024
abb8b4f
fix generic menu import
Dnouv Sep 27, 2024
f2f7dff
fix enum import
Dnouv Sep 27, 2024
d43a644
remove unused interaction
Dnouv Sep 27, 2024
452399c
Merge branch 'develop' into new/app_ui_menu
Dnouv Sep 27, 2024
6dcd1e2
update changeset
Dnouv Sep 30, 2024
631a471
reduce reduce
Dnouv Sep 30, 2024
e840508
Merge branch 'develop' into new/app_ui_menu
Dnouv Sep 30, 2024
d93ad3e
add newline
Dnouv Sep 30, 2024
f95c4f9
update changeset
Dnouv Oct 1, 2024
ccb6e46
Merge branch 'develop' into new/app_ui_menu
Dnouv Oct 7, 2024
d238277
Avoid any conversion
tassoevan Oct 8, 2024
d587947
Merge branch 'develop' into new/app_ui_menu
kodiakhq[bot] Oct 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
---

New UI context for Rocket.Chat Apps to register actions, specifically targeting AI Actions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import React, { memo, useMemo, useRef } from 'react';
import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction';
import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction';
import { useEmojiPickerData } from '../../../contexts/EmojiPickerContext';
import { useMessageActionAppsActionButtons } from '../../../hooks/useAppActionButtons';
import { useMessageActionAppsActionButtons, useMessageToolbarStarsAppsAction } from '../../../hooks/useAppActionButtons';
import { useEmbeddedLayout } from '../../../hooks/useEmbeddedLayout';
import EmojiElement from '../../../views/composer/EmojiPicker/EmojiElement';
import { useIsSelecting } from '../../../views/room/MessageList/contexts/SelectedMessagesContext';
import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoTranslate';
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 = useMessageToolbarStarsAppsAction(context);

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,98 @@
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
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';
import GenericMenu from '../../GenericMenu/GenericMenu';
import type { GenericMenuItemProps } from '../../GenericMenu/GenericMenuItem';

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
.map((option) => ({
Dnouv marked this conversation as resolved.
Show resolved Hide resolved
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) }) }),
}))
.reduce((acc, option) => {
const group = option.type ? option.type : '';
const section = acc.find((section: { id: string }) => section.id === group);
if (section) {
section.items.push(option);
return acc;
}

const newSection = { id: group, title: '', items: [option] };
acc.push(newSection);
return acc;
}, [] as unknown as MessageActionSection[])
Dnouv marked this conversation as resolved.
Show resolved Hide resolved
.map((section) => {
if (section.id !== 'apps') {
return section;
}

if (!isMessageEncrypted) {
return section;
}

return {
id: 'apps',
title: t('Apps'),
items: [
{
content: t('Unavailable'),
type: 'apps',
id,
disabled: true,
gap: false,
tooltip: t('Action_not_available_encrypted_content', { action: t('Apps') }),
},
],
};
});

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;
60 changes: 60 additions & 0 deletions apps/meteor/client/hooks/useAppActionButtons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,63 @@ export const useMessageActionAppsActionButtons = (context?: MessageActionContext
data,
} as UseQueryResult<MessageActionConfig[]>;
};

export const useMessageToolbarStarsAppsAction = (context?: MessageActionContext) => {
const result = useAppActionButtons('messageToolbarStarsAction');
const actionManager = useUiKitActionManager();
const applyButtonFilters = useApplyButtonFilters();
const dispatchToastMessage = useToastMessageDispatch();
const { t } = useTranslation();
const data = useMemo(
() =>
result.data
?.filter((action) => {
if (
context &&
!(action.when?.messageActionContext || ['message', 'message-mobile', 'threads', 'starred']).includes(context as any)
rique223 marked this conversation as resolved.
Show resolved Hide resolved
) {
return false;
}
return applyButtonFilters(action);
})
.map((action) => {
const item: MessageActionConfig = {
icon: undefined as any,
rique223 marked this conversation as resolved.
Show resolved Hide resolved
id: getIdForActionButton(action),
label: Utilities.getI18nKeyForApp(action.labelI18n, action.appId),
order: 7,
type: 'apps',
variant: action.variant,
action: (_, params) => {
void actionManager
.emitInteraction(action.appId, {
type: 'actionButton',
rid: params.message.rid,
tmid: params.message.tmid,
mid: params.message._id,
actionId: action.actionId,
payload: { context: action.context },
})
.catch(async (reason) => {
if (reason instanceof UiKitTriggerTimeoutError) {
dispatchToastMessage({
type: 'error',
message: t('UIKit_Interaction_Timeout'),
});
return;
}

return reason;
});
},
};

return item;
}),
[actionManager, applyButtonFilters, context, dispatchToastMessage, result.data, t],
);
return {
...result,
data,
} as UseQueryResult<MessageActionConfig[]>;
};
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 @@ -389,6 +389,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
17 changes: 16 additions & 1 deletion packages/ui-kit/src/interactions/UserInteraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ export type MesssageActionButtonUserInteraction = {
triggerId: string;
};

export type MessageToolbarStarsActionButtonUserInteraction = {
type: 'actionButton';
actionId: string;
payload: {
context: 'messageToolbarStarsAction';
Dnouv marked this conversation as resolved.
Show resolved Hide resolved
message?: undefined;
};
mid: string;
tmid?: string;
rid: string;
triggerId: string;
};

export type RoomActionButtonUserInteraction = {
type: 'actionButton';
actionId: string;
Expand All @@ -119,7 +132,8 @@ export type UserInteraction =
| MessageBoxActionButtonUserInteraction
| UserDropdownActionButtonUserInteraction
| MesssageActionButtonUserInteraction
| RoomActionButtonUserInteraction;
| RoomActionButtonUserInteraction
| MessageToolbarStarsActionButtonUserInteraction;

export const isMessageBlockActionUserInteraction = typia.createIs<MessageBlockActionUserInteraction>();
export const isViewBlockActionUserInteraction = typia.createIs<ViewBlockActionUserInteraction>();
Expand All @@ -129,3 +143,4 @@ export const isMessageBoxActionButtonUserInteraction = typia.createIs<MessageBox
export const isUserDropdownActionButtonUserInteraction = typia.createIs<UserDropdownActionButtonUserInteraction>();
export const isMesssageActionButtonUserInteraction = typia.createIs<MesssageActionButtonUserInteraction>();
export const isRoomActionButtonUserInteraction = typia.createIs<RoomActionButtonUserInteraction>();
export const isMessageToolbarStarsActionButtonUserInteraction = typia.createIs<MessageToolbarStarsActionButtonUserInteraction>();
Loading