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",