diff --git a/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx b/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx
index 3c3a14f1ce..ab0f6b09b8 100644
--- a/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx
@@ -6,6 +6,7 @@ import Icon from "/imports/ui/components/common/icon/component";
import { SMALL_VIEWPORT_BREAKPOINT } from '/imports/ui/components/layout/enums';
import KEY_CODES from '/imports/utils/keyCodes';
import MenuSkeleton from './skeleton';
+import GenericContentItem from '/imports/ui/components/generic-content/generic-content-item/component';
import Styled from './styles';
const intlMessages = defineMessages({
@@ -102,7 +103,8 @@ class BBBMenu extends React.Component {
return actions?.map(a => {
const { dataTest, label, onClick, key, disabled,
- description, selected, textColor, isToggle, loading } = a;
+ description, selected, textColor, isToggle, loading,
+ isTitle, titleActions, contentFunction } = a;
const emojiSelected = key?.toLowerCase()?.includes(selectedEmoji?.toLowerCase());
let customStyles = {
@@ -149,7 +151,7 @@ class BBBMenu extends React.Component {
isEmoji={isEmoji}
>
{a.icon ? : null}
- {label}
+ {label}
{description &&
{`${description}${selected ? ` - ${intl.formatMessage(intlMessages.active)}` : ''}`}
}
{a.iconRight ? : null}
@@ -158,11 +160,38 @@ class BBBMenu extends React.Component {
(!onClick && !a.isSeparator) && (
-
- {a.icon ? : null}
- {label}
- {a.iconRight ? : null}
+
+ {!contentFunction ? (
+ <>
+ {a.icon ? : null}
+ {label}
+ {a.iconRight ? : null}
+ {(isTitle && titleActions?.length > 0) ? (
+ titleActions.map((item, index) => (
+
+ ))
+ ) : null}
+ >
+ ) : (
+
+ )}
),
diff --git a/bigbluebutton-html5/imports/ui/components/common/menu/styles.js b/bigbluebutton-html5/imports/ui/components/common/menu/styles.js
index 6cdac88e62..3446ee3fcd 100644
--- a/bigbluebutton-html5/imports/ui/components/common/menu/styles.js
+++ b/bigbluebutton-html5/imports/ui/components/common/menu/styles.js
@@ -2,8 +2,14 @@ import styled from 'styled-components';
import Button from '/imports/ui/components/common/button/component';
import Icon from '/imports/ui/components/common/icon/component';
import MenuItem from '@mui/material/MenuItem';
-import { colorWhite, colorPrimary } from '/imports/ui/stylesheets/styled-components/palette';
-import { fontSizeLarge } from '/imports/ui/stylesheets/styled-components/typography';
+import {
+ colorWhite,
+ colorPrimary,
+} from '/imports/ui/stylesheets/styled-components/palette';
+import {
+ fontSizeLarge,
+ headingsFontWeight,
+} from '/imports/ui/stylesheets/styled-components/typography';
import { mediumUp } from '/imports/ui/stylesheets/styled-components/breakpoints';
import Menu from '@mui/material/Menu';
@@ -36,16 +42,37 @@ const MenuItemWrapper = styled.div`
flex-flow: column;
align-items: center;
`}
+ ${({ hasSpaceBetween }) => hasSpaceBetween && `
+ justify-content: space-between;
+ `}
+`;
+
+const TitleAction = styled(Button)`
+ z-index: 3;
+ margin-left: .1rem;
+ & > span:first-child {
+ margin: 0;
+ padding: 0;
+ }
`;
const Option = styled.div`
line-height: 1;
margin-right: 1.65rem;
- margin-left: .5rem;
+ ${({ hasIcon }) => hasIcon && `
+ margin-left: .5rem;
+ `}
white-space: normal;
overflow-wrap: anywhere;
padding: .1rem 0;
+ ${({ isTitle }) => isTitle && `
+ margin-left: .1rem;
+ padding: .1rem 0 0 0;
+ font-size: 1.1rem;
+ font-weight: ${headingsFontWeight};
+ `}
+
[dir="rtl"] & {
margin-right: .5rem;
margin-left: 1.65rem;
@@ -90,7 +117,15 @@ const IconRight = styled(Icon)`
`;
const BBBMenuInformation = styled.div`
- padding: 12px 16px;
+ ${({ isGenericContent }) => ((isGenericContent) ? `
+ padding: 0 16px;
+ ` : `
+ padding: 12px 16px;
+ `)}
+ ${({ isTitle }) => (isTitle) && `
+ min-width: 15rem;
+ padding: 12px 16px 8px 16px;
+ `}
margin: 0;
`;
@@ -164,6 +199,7 @@ const SkeletonWrapper = styled.span`
`;
export default {
+ TitleAction,
MenuWrapper,
MenuItemWrapper,
Option,
diff --git a/bigbluebutton-html5/imports/ui/components/generic-content/generic-content-item/component.tsx b/bigbluebutton-html5/imports/ui/components/generic-content/generic-content-item/component.tsx
index 4304bc57a7..926c0de36c 100644
--- a/bigbluebutton-html5/imports/ui/components/generic-content/generic-content-item/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/generic-content/generic-content-item/component.tsx
@@ -4,6 +4,7 @@ import { GenericContentItemProps } from './types';
const GenericContentItem: React.FC = (props) => {
const {
renderFunction,
+ width,
} = props;
const elementRef = useRef(null);
@@ -13,12 +14,16 @@ const GenericContentItem: React.FC = (props) => {
}
}, [elementRef]);
+ const style: React.CSSProperties = {
+ height: '100%',
+ overflow: 'hidden',
+ };
+ if (width) {
+ style.width = width;
+ }
return (
);
diff --git a/bigbluebutton-html5/imports/ui/components/generic-content/generic-content-item/types.ts b/bigbluebutton-html5/imports/ui/components/generic-content/generic-content-item/types.ts
index cce3db6ecb..86426fde7d 100644
--- a/bigbluebutton-html5/imports/ui/components/generic-content/generic-content-item/types.ts
+++ b/bigbluebutton-html5/imports/ui/components/generic-content/generic-content-item/types.ts
@@ -1,3 +1,4 @@
export interface GenericContentItemProps {
renderFunction: (element: HTMLElement) => void;
+ width?: string;
}
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-participants/page/component.tsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-participants/page/component.tsx
index c74c0600ad..dfd9692497 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-participants/page/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-participants/page/component.tsx
@@ -1,4 +1,5 @@
-import React, { useEffect, useRef } from 'react';
+import React, { useContext, useEffect, useRef } from 'react';
+import * as PluginSdk from 'bigbluebutton-html-plugin-sdk';
import useDeduplicatedSubscription from '/imports/ui/core/hooks/useDeduplicatedSubscription';
import { MEETING_PERMISSIONS_SUBSCRIPTION } from '../queries';
import { setLocalUserList, useLoadedUserList } from '/imports/ui/core/hooks/useLoadedUserList';
@@ -13,6 +14,7 @@ import ListItem from '../list-item/component';
import { layoutSelect } from '/imports/ui/components/layout/context';
import { Layout } from '/imports/ui/components/layout/layoutTypes';
import SkeletonUserListItem from '../list-item/skeleton/component';
+import { PluginsContext } from '/imports/ui/components/components-data/plugin-context/context';
interface UserListParticipantsContainerProps {
index: number;
@@ -36,6 +38,14 @@ const UsersListParticipantsPage: React.FC = ({
}) => {
const [openUserAction, setOpenUserAction] = React.useState(null);
const isRTL = layoutSelect((i: Layout) => i.isRTL);
+ const { pluginsExtensibleAreasAggregatedState } = useContext(PluginsContext);
+ let userListDropdownItems = [] as PluginSdk.UserListDropdownInterface[];
+ if (pluginsExtensibleAreasAggregatedState.userListDropdownItems) {
+ userListDropdownItems = [
+ ...pluginsExtensibleAreasAggregatedState.userListDropdownItems,
+ ];
+ }
+
return (
<>
{
@@ -49,6 +59,7 @@ const UsersListParticipantsPage: React.FC = ({
usersPolicies={meeting.usersPolicies}
isBreakout={meeting.isBreakout}
pageId={pageId}
+ userListDropdownItems={userListDropdownItems}
open={user.userId === openUserAction}
setOpenUserAction={setOpenUserAction}
>
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-participants/user-actions/component.tsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-participants/user-actions/component.tsx
index 11a131d865..8c0bf933aa 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-participants/user-actions/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-participants/user-actions/component.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useContext } from 'react';
+import React, { useState } from 'react';
import { User } from '/imports/ui/Types/user';
import { LockSettings, UsersPolicies } from '/imports/ui/Types/meeting';
import { useIntl, defineMessages } from 'react-intl';
@@ -34,7 +34,6 @@ import ConfirmationModal from '/imports/ui/components/common/modal/confirmation/
import BBBMenu from '/imports/ui/components/common/menu/component';
import { setPendingChat } from '/imports/ui/core/local-states/usePendingChat';
-import { PluginsContext } from '/imports/ui/components/components-data/plugin-context/context';
import Styled from './styles';
import { useMutation, useLazyQuery } from '@apollo/client';
import { CURRENT_PAGE_WRITERS_QUERY } from '/imports/ui/components/whiteboard/queries';
@@ -43,6 +42,7 @@ import useToggleVoice from '/imports/ui/components/audio/audio-graphql/hooks/use
import useWhoIsUnmuted from '/imports/ui/core/hooks/useWhoIsUnmuted';
interface UserActionsProps {
+ userListDropdownItems: PluginSdk.UserListDropdownInterface[];
user: User;
currentUser: User;
lockSettings: LockSettings;
@@ -56,14 +56,15 @@ interface UserActionsProps {
interface DropdownItem {
key: string;
- label: string | undefined;
- icon: string | undefined;
- tooltip: string | undefined;
- allowed: boolean | undefined;
- iconRight: string | undefined;
- textColor: string | undefined;
- isSeparator: boolean | undefined;
- onClick: (() => void) | undefined;
+ label?: string;
+ icon?: string;
+ tooltip?: string;
+ allowed?: boolean;
+ iconRight?: string;
+ textColor?: string;
+ isSeparator?: boolean;
+ contentFunction?: ((element: HTMLElement) => void);
+ onClick?: (() => void);
}
interface Writer {
@@ -171,8 +172,8 @@ const makeDropdownPluginItem: (
returnValue.onClick = dropdownButton.onClick;
break;
}
- case UserListDropdownItemType.INFORMATION: {
- const dropdownButton = userDropdownItem as PluginSdk.UserListDropdownInformation;
+ case UserListDropdownItemType.FIXED_CONTENT_INFORMATION: {
+ const dropdownButton = userDropdownItem as PluginSdk.UserListDropdownFixedContentInformation;
returnValue.label = dropdownButton.label;
returnValue.icon = dropdownButton.icon;
returnValue.iconRight = dropdownButton.iconRight;
@@ -180,6 +181,12 @@ const makeDropdownPluginItem: (
returnValue.allowed = dropdownButton.allowed;
break;
}
+ case UserListDropdownItemType.GENERIC_CONTENT_INFORMATION: {
+ const dropdownButton = userDropdownItem as PluginSdk.UserListDropdownGenericContentInformation;
+ returnValue.allowed = dropdownButton.allowed;
+ returnValue.contentFunction = dropdownButton.contentFunction;
+ break;
+ }
case UserListDropdownItemType.SEPARATOR: {
returnValue.allowed = true;
returnValue.isSeparator = true;
@@ -200,6 +207,7 @@ const UserActions: React.FC = ({
isBreakout,
children,
pageId,
+ userListDropdownItems,
open,
setOpenUserAction,
}) => {
@@ -251,8 +259,6 @@ const UserActions: React.FC = ({
}
};
- const { pluginsExtensibleAreasAggregatedState } = useContext(PluginsContext);
-
const { data: unmutedUsers } = useWhoIsUnmuted();
const isMuted = !unmutedUsers[user.userId];
@@ -283,13 +289,6 @@ const UserActions: React.FC = ({
const userChatLocked = user.userLockSettings?.disablePublicChat;
- let userListDropdownItems = [] as PluginSdk.UserListDropdownInterface[];
- if (pluginsExtensibleAreasAggregatedState.userListDropdownItems) {
- userListDropdownItems = [
- ...pluginsExtensibleAreasAggregatedState.userListDropdownItems,
- ];
- }
-
const userDropdownItems = userListDropdownItems.filter(
(item: PluginSdk.UserListDropdownInterface) => (user?.userId === item?.userId),
);
@@ -325,10 +324,25 @@ const UserActions: React.FC = ({
});
}
};
-
+ const titleActions = userDropdownItems.filter(
+ (item: PluginSdk.UserListDropdownInterface) => (
+ item?.type === UserListDropdownItemType.TITLE_ACTION),
+ );
const dropdownOptions = [
+ {
+ allowed: true,
+ key: 'userName',
+ label: user.name,
+ titleActions,
+ isTitle: true,
+ },
...makeDropdownPluginItem(userDropdownItems.filter(
- (item: PluginSdk.UserListDropdownInterface) => (item?.type === UserListDropdownItemType.INFORMATION),
+ (item: PluginSdk.UserListDropdownInterface) => (
+ item?.type === UserListDropdownItemType.FIXED_CONTENT_INFORMATION
+ || item?.type === UserListDropdownItemType.GENERIC_CONTENT_INFORMATION
+ || (item?.type === UserListDropdownItemType.SEPARATOR
+ && (item as PluginSdk.UserListDropdownSeparator)?.position
+ === PluginSdk.UserListDropdownSeparatorPosition.BEFORE)),
)),
{
allowed: user.cameras.length > 0
@@ -540,7 +554,13 @@ const UserActions: React.FC = ({
dataTest: 'ejectCamera',
},
...makeDropdownPluginItem(userDropdownItems.filter(
- (item: PluginSdk.UserListDropdownInterface) => (item?.type !== UserListDropdownItemType.INFORMATION),
+ (item: PluginSdk.UserListDropdownInterface) => (
+ item?.type !== UserListDropdownItemType.FIXED_CONTENT_INFORMATION
+ && item?.type !== UserListDropdownItemType.GENERIC_CONTENT_INFORMATION
+ && !(item?.type === UserListDropdownItemType.SEPARATOR
+ && (item as PluginSdk.UserListDropdownSeparator)?.position
+ === PluginSdk.UserListDropdownSeparatorPosition.BEFORE)
+ ),
)),
];
diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json
index e5cd6214f7..2abcf858a8 100644
--- a/bigbluebutton-html5/package-lock.json
+++ b/bigbluebutton-html5/package-lock.json
@@ -27,7 +27,7 @@
"autoprefixer": "^10.4.4",
"axios": "^1.7.4",
"babel-runtime": "~6.26.0",
- "bigbluebutton-html-plugin-sdk": "0.0.58",
+ "bigbluebutton-html-plugin-sdk": "0.0.59",
"bowser": "^2.11.0",
"browser-bunyan": "^1.8.0",
"classnames": "^2.2.6",
@@ -5975,9 +5975,9 @@
}
},
"node_modules/bigbluebutton-html-plugin-sdk": {
- "version": "0.0.58",
- "resolved": "https://registry.npmjs.org/bigbluebutton-html-plugin-sdk/-/bigbluebutton-html-plugin-sdk-0.0.58.tgz",
- "integrity": "sha512-x4Dnt2TiWlosM8vOqq/FCsuG8XCZ//Ab79b/5w7S8nRgeXWmIc942R4tbWZ7CwBGAdGnZvp3mAAqkvB7SYfk0g==",
+ "version": "0.0.59",
+ "resolved": "https://registry.npmjs.org/bigbluebutton-html-plugin-sdk/-/bigbluebutton-html-plugin-sdk-0.0.59.tgz",
+ "integrity": "sha512-HYmV9vkbC8M3CcKizCJMzgYaEA9w3fbRbuGtqDhHFQ0hFrsGP/Cd86gT+toFXKNoG1KatYwhZdMXk4eXNMjywg==",
"license": "LGPL-3.0",
"dependencies": {
"@apollo/client": "^3.8.7",
diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json
index 6c529b4819..4e594d4665 100644
--- a/bigbluebutton-html5/package.json
+++ b/bigbluebutton-html5/package.json
@@ -56,7 +56,7 @@
"autoprefixer": "^10.4.4",
"axios": "^1.7.4",
"babel-runtime": "~6.26.0",
- "bigbluebutton-html-plugin-sdk": "0.0.58",
+ "bigbluebutton-html-plugin-sdk": "0.0.59",
"bowser": "^2.11.0",
"browser-bunyan": "^1.8.0",
"classnames": "^2.2.6",