Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
6 changes: 6 additions & 0 deletions .changeset/big-teachers-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/ui-contexts": minor
---

Add the possibility to hide some elements through postMessage events.
3 changes: 2 additions & 1 deletion apps/meteor/app/ui-utils/client/lib/messageBox.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { IMessage, IRoom } from '@rocket.chat/core-typings';
import type { Keys as IconName } from '@rocket.chat/icons';
import type { TranslationKey } from '@rocket.chat/ui-contexts';

import type { ChatAPI } from '../../../../client/lib/chats/ChatAPI';

export type MessageBoxAction = {
label: TranslationKey;
id: string;
icon?: string;
icon: IconName;
action: (params: { rid: IRoom['_id']; tmid?: IMessage['_id']; event: Event; chat: ChatAPI }) => void;
condition?: () => boolean;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { IMessage, IRoom, ISubscription, ITranslatedMessage } from '@rocket
import { isThreadMessage, isRoomFederated, isVideoConfMessage } from '@rocket.chat/core-typings';
import { MessageToolbox as FuselageMessageToolbox, MessageToolboxItem } from '@rocket.chat/fuselage';
import { useFeaturePreview } from '@rocket.chat/ui-client';
import { useUser, useSettings, useTranslation, useMethod } from '@rocket.chat/ui-contexts';
import { useUser, useSettings, useTranslation, useMethod, useLayoutHiddenActions } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type { ReactElement } from 'react';
import React, { memo, useMemo } from 'react';
Expand Down Expand Up @@ -70,13 +70,18 @@ const MessageToolbox = ({

const actionButtonApps = useMessageActionAppsActionButtons(context);

const { messageToolbox: hiddenActions } = useLayoutHiddenActions();

const actionsQueryResult = useQuery(['rooms', room._id, 'messages', message._id, 'actions'] as const, async () => {
const props = { message, room, user, subscription, settings: mapSettings, chat };

const toolboxItems = await MessageAction.getAll(props, context, 'message');
const menuItems = await MessageAction.getAll(props, context, 'menu');

return { message: toolboxItems, menu: menuItems };
return {
message: toolboxItems.filter((action) => !hiddenActions.includes(action.id)),
menu: menuItems.filter((action) => !hiddenActions.includes(action.id)),
};
});

const toolbox = useRoomToolbox();
Expand All @@ -85,7 +90,7 @@ const MessageToolbox = ({

const autoTranslateOptions = useAutoTranslate(subscription);

if (selecting) {
if (selecting || (!actionsQueryResult.data?.message.length && !actionsQueryResult.data?.menu.length)) {
return null;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/client/hooks/useAppActionButtons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const useMessageboxAppsActionButtons = () => {
return applyButtonFilters(action);
})
.map((action) => {
const item: MessageBoxAction = {
const item: Omit<MessageBoxAction, 'icon'> = {
id: getIdForActionButton(action),
label: Utilities.getI18nKeyForApp(action.labelI18n, action.appId),
action: (params) => {
Expand Down
23 changes: 23 additions & 0 deletions apps/meteor/client/hooks/useFileInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useRef, useEffect } from 'react';
import type { AllHTMLAttributes } from 'react';

export const useFileInput = (props: AllHTMLAttributes<HTMLInputElement>) => {
const ref = useRef<HTMLInputElement>();

useEffect(() => {
const fileInput = document.createElement('input');
fileInput.setAttribute('style', 'display: none;');
Object.entries(props).forEach(([key, value]) => {
fileInput.setAttribute(key, value);
});
document.body.appendChild(fileInput);
ref.current = fileInput;

return (): void => {
ref.current = undefined;
fileInput.remove();
};
}, [props]);

return ref;
};
23 changes: 22 additions & 1 deletion apps/meteor/client/providers/LayoutProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@ import { LayoutContext, useRouter, useSetting } from '@rocket.chat/ui-contexts';
import type { FC } from 'react';
import React, { useMemo, useState, useEffect } from 'react';

const hiddenActionsDefaultValue = {
roomToolbox: [],
messageToolbox: [],
composerToolbox: [],
userToolbox: [],
};

const LayoutProvider: FC = ({ children }) => {
const showTopNavbarEmbeddedLayout = Boolean(useSetting('UI_Show_top_navbar_embedded_layout'));
const [isCollapsed, setIsCollapsed] = useState(false);
const breakpoints = useBreakpoints(); // ["xs", "sm", "md", "lg", "xl", xxl"]
const [hiddenActions, setHiddenActions] = useState(hiddenActionsDefaultValue);

const router = useRouter();
// Once the layout is embedded, it can't be changed
Expand All @@ -18,6 +26,18 @@ const LayoutProvider: FC = ({ children }) => {
setIsCollapsed(isMobile);
}, [isMobile]);

useEffect(() => {
const eventHandler = (event: MessageEvent<any>) => {
if (event.data?.event !== 'overrideUi') {
return;
}

setHiddenActions({ ...hiddenActionsDefaultValue, ...event.data.hideActions });
};
window.addEventListener('message', eventHandler);
return () => window.removeEventListener('message', eventHandler);
}, []);

return (
<LayoutContext.Provider
children={children}
Expand All @@ -42,8 +62,9 @@ const LayoutProvider: FC = ({ children }) => {
contextualBarExpanded: breakpoints.includes('sm'),
// eslint-disable-next-line no-nested-ternary
contextualBarPosition: breakpoints.includes('sm') ? (breakpoints.includes('lg') ? 'relative' : 'absolute') : 'fixed',
hiddenActions,
}),
[isMobile, isEmbedded, showTopNavbarEmbeddedLayout, isCollapsed, breakpoints, router],
[isMobile, isEmbedded, showTopNavbarEmbeddedLayout, isCollapsed, breakpoints, router, hiddenActions],
)}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const RoomToolbox = ({ className }: RoomToolboxProps) => {
{featuredActions.map(mapToToolboxItem)}
{featuredActions.length > 0 && <HeaderToolboxDivider />}
{visibleActions.map(mapToToolboxItem)}
{(normalActions.length > 6 || !roomToolboxExpanded) && (
{(normalActions.length > 6 || !roomToolboxExpanded) && !!hiddenActions.length && (
<GenericMenu title={t('Options')} data-qa-id='ToolBox-Menu' sections={hiddenActions} placement='bottom-end' />
)}
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable complexity */
import type { IMessage, ISubscription } from '@rocket.chat/core-typings';
import { Button, Tag, Box } from '@rocket.chat/fuselage';
import { useContentBoxSize, useMutableCallback } from '@rocket.chat/fuselage-hooks';
Expand Down Expand Up @@ -410,15 +411,14 @@ const MessageBox = ({
disabled={isRecording || !canSend}
/>
)}
<MessageComposerActionsDivider />
<MessageBoxActionsToolbar
variant={sizes.inlineSize < 480 ? 'small' : 'large'}
isRecording={isRecording}
typing={typing}
canSend={canSend}
typing={typing}
isMicrophoneDenied={isMicrophoneDenied}
rid={room._id}
tmid={tmid}
isMicrophoneDenied={isMicrophoneDenied}
isRecording={isRecording}
variant={sizes.inlineSize < 480 ? 'small' : 'large'}
/>
</MessageComposerToolbarActions>
<MessageComposerToolbarSubmit>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,106 +1,26 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { Dropdown, IconButton, Option, OptionTitle, OptionIcon, OptionContent } from '@rocket.chat/fuselage';
import { useTranslation, useUserRoom } from '@rocket.chat/ui-contexts';
import type { ComponentProps, ReactNode } from 'react';
import React, { useRef, Fragment } from 'react';
import { Dropdown, IconButton } from '@rocket.chat/fuselage';
import type { ReactNode } from 'react';
import React, { useRef } from 'react';

import { messageBox } from '../../../../../../app/ui-utils/client';
import { useMessageboxAppsActionButtons } from '../../../../../hooks/useAppActionButtons';
import type { ChatAPI } from '../../../../../lib/chats/ChatAPI';
import { useDropdownVisibility } from '../../../../../sidebar/header/hooks/useDropdownVisibility';
import { useChat } from '../../../contexts/ChatContext';
import CreateDiscussionAction from './actions/CreateDiscussionAction';
import ShareLocationAction from './actions/ShareLocationAction';
import WebdavAction from './actions/WebdavAction';

type ActionsToolbarDropdownProps = {
chatContext?: ChatAPI;
rid: IRoom['_id'];
isRecording?: boolean;
tmid?: string;
actions?: ReactNode[];
disabled?: boolean;
children: () => ReactNode[];
};

const ActionsToolbarDropdown = ({ isRecording, rid, tmid, actions, ...props }: ActionsToolbarDropdownProps) => {
const chatContext = useChat();

if (!chatContext) {
throw new Error('useChat must be used within a ChatProvider');
}

const t = useTranslation();
const ActionsToolbarDropdown = ({ children, ...props }: ActionsToolbarDropdownProps) => {
const reference = useRef(null);
const target = useRef(null);

const room = useUserRoom(rid);

const { isVisible, toggle } = useDropdownVisibility({ reference, target });

const apps = useMessageboxAppsActionButtons();

const groups = {
...(apps.isSuccess &&
apps.data.length > 0 && {
Apps: apps.data,
}),
...messageBox.actions.get(),
};

const messageBoxActions = Object.entries(groups).map(([name, group]) => {
const items = group.map((item) => ({
icon: item.icon,
name: t(item.label),
type: 'messagebox-action',
id: item.id,
action: item.action,
}));

return {
title: t.has(name) && t(name),
items,
};
});

return (
<>
<IconButton
data-qa-id='menu-more-actions'
disabled={isRecording}
small
ref={reference}
icon='plus'
onClick={() => toggle()}
{...props}
/>
<IconButton data-qa-id='menu-more-actions' small ref={reference} icon='plus' onClick={() => toggle()} {...props} />
{isVisible && (
<Dropdown reference={reference} ref={target} placement='bottom-start'>
<OptionTitle>{t('Create_new')}</OptionTitle>
{room && <CreateDiscussionAction room={room} />}
{actions}
<WebdavAction chatContext={chatContext} />
{room && <ShareLocationAction room={room} tmid={tmid} />}
{messageBoxActions?.map((actionGroup, index) => (
<Fragment key={index}>
<OptionTitle>{actionGroup.title}</OptionTitle>
{actionGroup.items.map((item) => (
<Option
key={item.id}
onClick={(event) =>
item.action({
rid,
tmid,
event: event as unknown as Event,
chat: chatContext,
})
}
gap={!item.icon}
>
{item.icon && <OptionIcon name={item.icon as ComponentProps<typeof OptionIcon>['name']} />}
<OptionContent>{item.name}</OptionContent>
</Option>
))}
</Fragment>
))}
{children()}
</Dropdown>
)}
</>
Expand Down
Loading