Merge pull request #21278 from GuiLeme/lms-plugin-adaptation

This commit is contained in:
Tiago Jacobs 2024-10-02 21:12:33 -03:00 committed by GitHub
commit eecd48ae30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 146 additions and 44 deletions

View File

@ -6,6 +6,7 @@ import Icon from "/imports/ui/components/common/icon/component";
import { SMALL_VIEWPORT_BREAKPOINT } from '/imports/ui/components/layout/enums'; import { SMALL_VIEWPORT_BREAKPOINT } from '/imports/ui/components/layout/enums';
import KEY_CODES from '/imports/utils/keyCodes'; import KEY_CODES from '/imports/utils/keyCodes';
import MenuSkeleton from './skeleton'; import MenuSkeleton from './skeleton';
import GenericContentItem from '/imports/ui/components/generic-content/generic-content-item/component';
import Styled from './styles'; import Styled from './styles';
const intlMessages = defineMessages({ const intlMessages = defineMessages({
@ -102,7 +103,8 @@ class BBBMenu extends React.Component {
return actions?.map(a => { return actions?.map(a => {
const { dataTest, label, onClick, key, disabled, 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()); const emojiSelected = key?.toLowerCase()?.includes(selectedEmoji?.toLowerCase());
let customStyles = { let customStyles = {
@ -149,7 +151,7 @@ class BBBMenu extends React.Component {
isEmoji={isEmoji} isEmoji={isEmoji}
> >
{a.icon ? <Icon iconName={a.icon} key="icon" /> : null} {a.icon ? <Icon iconName={a.icon} key="icon" /> : null}
<Styled.Option isHorizontal={isHorizontal} isMobile={isMobile} aria-describedby={`${key}-option-desc`}>{label}</Styled.Option> <Styled.Option hasIcon={!!(a.icon)} isHorizontal={isHorizontal} isMobile={isMobile} aria-describedby={`${key}-option-desc`}>{label}</Styled.Option>
{description && <div className="sr-only" id={`${key}-option-desc`}>{`${description}${selected ? ` - ${intl.formatMessage(intlMessages.active)}` : ''}`}</div>} {description && <div className="sr-only" id={`${key}-option-desc`}>{`${description}${selected ? ` - ${intl.formatMessage(intlMessages.active)}` : ''}`}</div>}
{a.iconRight ? <Styled.IconRight iconName={a.iconRight} key="iconRight" /> : null} {a.iconRight ? <Styled.IconRight iconName={a.iconRight} key="iconRight" /> : null}
</Styled.MenuItemWrapper> </Styled.MenuItemWrapper>
@ -158,11 +160,38 @@ class BBBMenu extends React.Component {
(!onClick && !a.isSeparator) && ( (!onClick && !a.isSeparator) && (
<Styled.BBBMenuInformation <Styled.BBBMenuInformation
key={a.key} key={a.key}
isTitle={isTitle}
isGenericContent={!!contentFunction}
> >
<Styled.MenuItemWrapper> <Styled.MenuItemWrapper
{a.icon ? <Icon color={textColor} iconName={a.icon} key="icon" /> : null} hasSpaceBetween={isTitle && titleActions}
<Styled.Option textColor={textColor} isHorizontal={isHorizontal} isMobile={isMobile} aria-describedby={`${key}-option-desc`}>{label}</Styled.Option> >
{a.iconRight ? <Styled.IconRight color={textColor} iconName={a.iconRight} key="iconRight" /> : null} {!contentFunction ? (
<>
{a.icon ? <Icon color={textColor} iconName={a.icon} key="icon" /> : null}
<Styled.Option hasIcon={!!(a.icon)} isTitle={isTitle} textColor={textColor} isHorizontal={isHorizontal} isMobile={isMobile} aria-describedby={`${key}-option-desc`}>{label}</Styled.Option>
{a.iconRight ? <Styled.IconRight color={textColor} iconName={a.iconRight} key="iconRight" /> : null}
{(isTitle && titleActions?.length > 0) ? (
titleActions.map((item, index) => (
<Styled.TitleAction
key={item.id || index}
tooltipplacement="right"
size="md"
onClick={item.onClick}
circle
tooltipLabel={item.tooltip}
hideLabel
icon={item.icon}
/>
))
) : null}
</>
) : (
<GenericContentItem
width="100%"
renderFunction={contentFunction}
/>
)}
</Styled.MenuItemWrapper> </Styled.MenuItemWrapper>
</Styled.BBBMenuInformation> </Styled.BBBMenuInformation>
), ),

View File

@ -2,8 +2,14 @@ import styled from 'styled-components';
import Button from '/imports/ui/components/common/button/component'; import Button from '/imports/ui/components/common/button/component';
import Icon from '/imports/ui/components/common/icon/component'; import Icon from '/imports/ui/components/common/icon/component';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import { colorWhite, colorPrimary } from '/imports/ui/stylesheets/styled-components/palette'; import {
import { fontSizeLarge } from '/imports/ui/stylesheets/styled-components/typography'; 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 { mediumUp } from '/imports/ui/stylesheets/styled-components/breakpoints';
import Menu from '@mui/material/Menu'; import Menu from '@mui/material/Menu';
@ -36,16 +42,37 @@ const MenuItemWrapper = styled.div`
flex-flow: column; flex-flow: column;
align-items: center; 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` const Option = styled.div`
line-height: 1; line-height: 1;
margin-right: 1.65rem; margin-right: 1.65rem;
margin-left: .5rem; ${({ hasIcon }) => hasIcon && `
margin-left: .5rem;
`}
white-space: normal; white-space: normal;
overflow-wrap: anywhere; overflow-wrap: anywhere;
padding: .1rem 0; padding: .1rem 0;
${({ isTitle }) => isTitle && `
margin-left: .1rem;
padding: .1rem 0 0 0;
font-size: 1.1rem;
font-weight: ${headingsFontWeight};
`}
[dir="rtl"] & { [dir="rtl"] & {
margin-right: .5rem; margin-right: .5rem;
margin-left: 1.65rem; margin-left: 1.65rem;
@ -90,7 +117,15 @@ const IconRight = styled(Icon)`
`; `;
const BBBMenuInformation = styled.div` 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; margin: 0;
`; `;
@ -164,6 +199,7 @@ const SkeletonWrapper = styled.span`
`; `;
export default { export default {
TitleAction,
MenuWrapper, MenuWrapper,
MenuItemWrapper, MenuItemWrapper,
Option, Option,

View File

@ -4,6 +4,7 @@ import { GenericContentItemProps } from './types';
const GenericContentItem: React.FC<GenericContentItemProps> = (props) => { const GenericContentItem: React.FC<GenericContentItemProps> = (props) => {
const { const {
renderFunction, renderFunction,
width,
} = props; } = props;
const elementRef = useRef(null); const elementRef = useRef(null);
@ -13,12 +14,16 @@ const GenericContentItem: React.FC<GenericContentItemProps> = (props) => {
} }
}, [elementRef]); }, [elementRef]);
const style: React.CSSProperties = {
height: '100%',
overflow: 'hidden',
};
if (width) {
style.width = width;
}
return ( return (
<div <div
style={{ style={style}
height: '100%',
overflow: 'hidden',
}}
ref={elementRef} ref={elementRef}
/> />
); );

View File

@ -1,3 +1,4 @@
export interface GenericContentItemProps { export interface GenericContentItemProps {
renderFunction: (element: HTMLElement) => void; renderFunction: (element: HTMLElement) => void;
width?: string;
} }

View File

@ -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 useDeduplicatedSubscription from '/imports/ui/core/hooks/useDeduplicatedSubscription';
import { MEETING_PERMISSIONS_SUBSCRIPTION } from '../queries'; import { MEETING_PERMISSIONS_SUBSCRIPTION } from '../queries';
import { setLocalUserList, useLoadedUserList } from '/imports/ui/core/hooks/useLoadedUserList'; 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 { layoutSelect } from '/imports/ui/components/layout/context';
import { Layout } from '/imports/ui/components/layout/layoutTypes'; import { Layout } from '/imports/ui/components/layout/layoutTypes';
import SkeletonUserListItem from '../list-item/skeleton/component'; import SkeletonUserListItem from '../list-item/skeleton/component';
import { PluginsContext } from '/imports/ui/components/components-data/plugin-context/context';
interface UserListParticipantsContainerProps { interface UserListParticipantsContainerProps {
index: number; index: number;
@ -36,6 +38,14 @@ const UsersListParticipantsPage: React.FC<UsersListParticipantsPage> = ({
}) => { }) => {
const [openUserAction, setOpenUserAction] = React.useState<string | null>(null); const [openUserAction, setOpenUserAction] = React.useState<string | null>(null);
const isRTL = layoutSelect((i: Layout) => i.isRTL); const isRTL = layoutSelect((i: Layout) => i.isRTL);
const { pluginsExtensibleAreasAggregatedState } = useContext(PluginsContext);
let userListDropdownItems = [] as PluginSdk.UserListDropdownInterface[];
if (pluginsExtensibleAreasAggregatedState.userListDropdownItems) {
userListDropdownItems = [
...pluginsExtensibleAreasAggregatedState.userListDropdownItems,
];
}
return ( return (
<> <>
{ {
@ -49,6 +59,7 @@ const UsersListParticipantsPage: React.FC<UsersListParticipantsPage> = ({
usersPolicies={meeting.usersPolicies} usersPolicies={meeting.usersPolicies}
isBreakout={meeting.isBreakout} isBreakout={meeting.isBreakout}
pageId={pageId} pageId={pageId}
userListDropdownItems={userListDropdownItems}
open={user.userId === openUserAction} open={user.userId === openUserAction}
setOpenUserAction={setOpenUserAction} setOpenUserAction={setOpenUserAction}
> >

View File

@ -1,4 +1,4 @@
import React, { useState, useContext } from 'react'; import React, { useState } from 'react';
import { User } from '/imports/ui/Types/user'; import { User } from '/imports/ui/Types/user';
import { LockSettings, UsersPolicies } from '/imports/ui/Types/meeting'; import { LockSettings, UsersPolicies } from '/imports/ui/Types/meeting';
import { useIntl, defineMessages } from 'react-intl'; 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 BBBMenu from '/imports/ui/components/common/menu/component';
import { setPendingChat } from '/imports/ui/core/local-states/usePendingChat'; 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 Styled from './styles';
import { useMutation, useLazyQuery } from '@apollo/client'; import { useMutation, useLazyQuery } from '@apollo/client';
import { CURRENT_PAGE_WRITERS_QUERY } from '/imports/ui/components/whiteboard/queries'; 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'; import useWhoIsUnmuted from '/imports/ui/core/hooks/useWhoIsUnmuted';
interface UserActionsProps { interface UserActionsProps {
userListDropdownItems: PluginSdk.UserListDropdownInterface[];
user: User; user: User;
currentUser: User; currentUser: User;
lockSettings: LockSettings; lockSettings: LockSettings;
@ -56,14 +56,15 @@ interface UserActionsProps {
interface DropdownItem { interface DropdownItem {
key: string; key: string;
label: string | undefined; label?: string;
icon: string | undefined; icon?: string;
tooltip: string | undefined; tooltip?: string;
allowed: boolean | undefined; allowed?: boolean;
iconRight: string | undefined; iconRight?: string;
textColor: string | undefined; textColor?: string;
isSeparator: boolean | undefined; isSeparator?: boolean;
onClick: (() => void) | undefined; contentFunction?: ((element: HTMLElement) => void);
onClick?: (() => void);
} }
interface Writer { interface Writer {
@ -171,8 +172,8 @@ const makeDropdownPluginItem: (
returnValue.onClick = dropdownButton.onClick; returnValue.onClick = dropdownButton.onClick;
break; break;
} }
case UserListDropdownItemType.INFORMATION: { case UserListDropdownItemType.FIXED_CONTENT_INFORMATION: {
const dropdownButton = userDropdownItem as PluginSdk.UserListDropdownInformation; const dropdownButton = userDropdownItem as PluginSdk.UserListDropdownFixedContentInformation;
returnValue.label = dropdownButton.label; returnValue.label = dropdownButton.label;
returnValue.icon = dropdownButton.icon; returnValue.icon = dropdownButton.icon;
returnValue.iconRight = dropdownButton.iconRight; returnValue.iconRight = dropdownButton.iconRight;
@ -180,6 +181,12 @@ const makeDropdownPluginItem: (
returnValue.allowed = dropdownButton.allowed; returnValue.allowed = dropdownButton.allowed;
break; break;
} }
case UserListDropdownItemType.GENERIC_CONTENT_INFORMATION: {
const dropdownButton = userDropdownItem as PluginSdk.UserListDropdownGenericContentInformation;
returnValue.allowed = dropdownButton.allowed;
returnValue.contentFunction = dropdownButton.contentFunction;
break;
}
case UserListDropdownItemType.SEPARATOR: { case UserListDropdownItemType.SEPARATOR: {
returnValue.allowed = true; returnValue.allowed = true;
returnValue.isSeparator = true; returnValue.isSeparator = true;
@ -200,6 +207,7 @@ const UserActions: React.FC<UserActionsProps> = ({
isBreakout, isBreakout,
children, children,
pageId, pageId,
userListDropdownItems,
open, open,
setOpenUserAction, setOpenUserAction,
}) => { }) => {
@ -251,8 +259,6 @@ const UserActions: React.FC<UserActionsProps> = ({
} }
}; };
const { pluginsExtensibleAreasAggregatedState } = useContext(PluginsContext);
const { data: unmutedUsers } = useWhoIsUnmuted(); const { data: unmutedUsers } = useWhoIsUnmuted();
const isMuted = !unmutedUsers[user.userId]; const isMuted = !unmutedUsers[user.userId];
@ -283,13 +289,6 @@ const UserActions: React.FC<UserActionsProps> = ({
const userChatLocked = user.userLockSettings?.disablePublicChat; const userChatLocked = user.userLockSettings?.disablePublicChat;
let userListDropdownItems = [] as PluginSdk.UserListDropdownInterface[];
if (pluginsExtensibleAreasAggregatedState.userListDropdownItems) {
userListDropdownItems = [
...pluginsExtensibleAreasAggregatedState.userListDropdownItems,
];
}
const userDropdownItems = userListDropdownItems.filter( const userDropdownItems = userListDropdownItems.filter(
(item: PluginSdk.UserListDropdownInterface) => (user?.userId === item?.userId), (item: PluginSdk.UserListDropdownInterface) => (user?.userId === item?.userId),
); );
@ -325,10 +324,25 @@ const UserActions: React.FC<UserActionsProps> = ({
}); });
} }
}; };
const titleActions = userDropdownItems.filter(
(item: PluginSdk.UserListDropdownInterface) => (
item?.type === UserListDropdownItemType.TITLE_ACTION),
);
const dropdownOptions = [ const dropdownOptions = [
{
allowed: true,
key: 'userName',
label: user.name,
titleActions,
isTitle: true,
},
...makeDropdownPluginItem(userDropdownItems.filter( ...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 allowed: user.cameras.length > 0
@ -540,7 +554,13 @@ const UserActions: React.FC<UserActionsProps> = ({
dataTest: 'ejectCamera', dataTest: 'ejectCamera',
}, },
...makeDropdownPluginItem(userDropdownItems.filter( ...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)
),
)), )),
]; ];

View File

@ -27,7 +27,7 @@
"autoprefixer": "^10.4.4", "autoprefixer": "^10.4.4",
"axios": "^1.7.4", "axios": "^1.7.4",
"babel-runtime": "~6.26.0", "babel-runtime": "~6.26.0",
"bigbluebutton-html-plugin-sdk": "0.0.58", "bigbluebutton-html-plugin-sdk": "0.0.59",
"bowser": "^2.11.0", "bowser": "^2.11.0",
"browser-bunyan": "^1.8.0", "browser-bunyan": "^1.8.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
@ -5975,9 +5975,9 @@
} }
}, },
"node_modules/bigbluebutton-html-plugin-sdk": { "node_modules/bigbluebutton-html-plugin-sdk": {
"version": "0.0.58", "version": "0.0.59",
"resolved": "https://registry.npmjs.org/bigbluebutton-html-plugin-sdk/-/bigbluebutton-html-plugin-sdk-0.0.58.tgz", "resolved": "https://registry.npmjs.org/bigbluebutton-html-plugin-sdk/-/bigbluebutton-html-plugin-sdk-0.0.59.tgz",
"integrity": "sha512-x4Dnt2TiWlosM8vOqq/FCsuG8XCZ//Ab79b/5w7S8nRgeXWmIc942R4tbWZ7CwBGAdGnZvp3mAAqkvB7SYfk0g==", "integrity": "sha512-HYmV9vkbC8M3CcKizCJMzgYaEA9w3fbRbuGtqDhHFQ0hFrsGP/Cd86gT+toFXKNoG1KatYwhZdMXk4eXNMjywg==",
"license": "LGPL-3.0", "license": "LGPL-3.0",
"dependencies": { "dependencies": {
"@apollo/client": "^3.8.7", "@apollo/client": "^3.8.7",

View File

@ -56,7 +56,7 @@
"autoprefixer": "^10.4.4", "autoprefixer": "^10.4.4",
"axios": "^1.7.4", "axios": "^1.7.4",
"babel-runtime": "~6.26.0", "babel-runtime": "~6.26.0",
"bigbluebutton-html-plugin-sdk": "0.0.58", "bigbluebutton-html-plugin-sdk": "0.0.59",
"bowser": "^2.11.0", "bowser": "^2.11.0",
"browser-bunyan": "^1.8.0", "browser-bunyan": "^1.8.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",