Skip to content

Conversation

@AtilaU19
Copy link

What does this PR do?

Port the new raise hands behavior to our new design. Due to design differences from version 3.0 to 3.1 pulling the modifications through chery-pick was not the best way, so the modifications were done manually based on prs.
Based on 2 PRs:

Closes Issue(s)

Closes None

More

Screencast from 18-11-2025 11:33:12.webm
Screenshot from 2025-12-15 17-15-37

Copilot AI review requested due to automatic review settings December 15, 2025 20:34
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR ports the new raise hand behavior from BigBlueButton version 3.0 to version 3.1, reimplementing the feature to match the new design system. The changes replace a toast-based notification system with an inline raised hands list directly in the user sidebar, providing better visibility and user management capabilities.

Key changes:

  • Replaces toast notifications with a dedicated raised hands section that displays users with raised hands in a separate scrollable list with indexed badges showing their order
  • Adds individual hand lowering capability through a new toolbar button that appears when viewing users in the raised-hands context
  • Implements "next in line" notification that alerts the first user in the raised hands queue when they're up next

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
bigbluebutton-html5/public/locales/en.json Updated localization strings for lower hands button and raised hands title
bigbluebutton-html5/imports/ui/core/graphql/queries/users.ts Added voice data to RaisedHandUser subscription query and type definition
bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/container.tsx Refactored to import RaisedHandUser type from centralized location
bigbluebutton-html5/imports/ui/components/user-list/user-list-participants/styles.ts Added styles for user name containers and updated list item styling
bigbluebutton-html5/imports/ui/components/user-list/user-list-participants/page/component.tsx Added type="participant" prop to differentiate from raised-hand context
bigbluebutton-html5/imports/ui/components/user-list/user-list-participants/list-item/user-item-toolbar/types.ts Made icon property optional to support text-only toolbar items
bigbluebutton-html5/imports/ui/components/user-list/user-list-participants/list-item/user-item-toolbar/styles.ts Added conditional styling for text-based toolbar items
bigbluebutton-html5/imports/ui/components/user-list/user-list-participants/list-item/user-item-toolbar/component.tsx Added logic to render text labels when no icon is provided
bigbluebutton-html5/imports/ui/components/user-list/user-list-participants/list-item/types.ts Added type discriminator and allowedToLowerHand permission
bigbluebutton-html5/imports/ui/components/user-list/user-list-participants/list-item/styles.ts Added RaiseHandAvatar styled component for raised hand display
bigbluebutton-html5/imports/ui/components/user-list/user-list-participants/list-item/service.ts Modified permissions logic to account for raised-hand vs participant contexts and added lower hand action
bigbluebutton-html5/imports/ui/components/user-list/user-list-participants/list-item/component.tsx Refactored to use new useUserOperations hook and removed inline mutation definitions
bigbluebutton-html5/imports/ui/components/user-list/user-list-participants/list-item/avatar-content/component.tsx Removed raised hand icon from avatar content (now shown separately)
bigbluebutton-html5/imports/ui/components/user-list/styles.ts Added SplitScrollContainer and ParticipantsScrollSection for dual-list layout
bigbluebutton-html5/imports/ui/components/user-list/raised-hands/styles.ts New file with styles for raised hands container, list, and clear button
bigbluebutton-html5/imports/ui/components/user-list/raised-hands/list/component.tsx New component rendering the list of raised hand users
bigbluebutton-html5/imports/ui/components/user-list/raised-hands/list-item/component.tsx New component for individual raised hand user items with toolbar actions
bigbluebutton-html5/imports/ui/components/user-list/raised-hands/component.tsx New container component managing raised hands state and lower-all functionality
bigbluebutton-html5/imports/ui/components/user-list/hooks/useUserOperations.ts New hook consolidating user operation mutations and modal management
bigbluebutton-html5/imports/ui/components/user-list/component.tsx Integrated RaisedHandsContainer into user list with conditional split-scroll layout
bigbluebutton-html5/imports/ui/components/raisehand-notifier/component.jsx Removed old toast-based notification system (206 lines deleted)
bigbluebutton-html5/imports/ui/components/app/hooks/useUserStatusNotifications.ts Added notification for when user becomes first in raised hands queue
bigbluebutton-html5/imports/ui/Types/user.ts Added RaisedHandUser interface and raiseHandTime property to User

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +201 to +202
titleMessageId="app.userList.menu.removeConfirmation.label"
titleMessageExtra={user.name}
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ConfirmationModal component doesn't accept titleMessageId and titleMessageExtra props. Based on the component's implementation, it expects a title prop with a pre-formatted string. This should be changed to use title={intl.formatMessage({ id: 'app.userList.menu.removeConfirmation.label' }, { userName: user.name })} instead.

Suggested change
titleMessageId="app.userList.menu.removeConfirmation.label"
titleMessageExtra={user.name}
title={intl.formatMessage({ id: 'app.userList.menu.removeConfirmation.label' }, { userName: user.name })}

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +48
export const mapRaisedHandToUser = (raisedHandUser: RaisedHandUser): User => {
const voiceData = raisedHandUser.voice || {};

const user: User = {
...raisedHandUser,

avatar: '',
cameras: [],
whiteboardWriteAccess: raisedHandUser.whiteboardWriteAccess ?? false,
presenter: raisedHandUser.presenter ?? false,
isModerator: raisedHandUser.isModerator ?? false,
raiseHand: raisedHandUser.raiseHand ?? true,
raiseHandTime: raisedHandUser.raiseHandTime,
locked: false,
voice: {
joined: voiceData.joined ?? false,
talking: false,
muted: false,
listenOnly: voiceData.listenOnly ?? false,
listenOnlyInputDevice: undefined,
deafened: voiceData.deafened ?? false,
},

emoji: 'none',
loggedOut: false,
guest: false,
authed: true,
waitingForAcceptance: false,
} as User;

return user;
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mapRaisedHandToUser function hardcodes several User properties that may not be accurate for raised hand users. Specifically: avatar: '', cameras: [], locked: false, emoji: 'none', loggedOut: false, guest: false, authed: true, waitingForAcceptance: false. These values might not reflect the actual state of the user. Consider whether these properties should be obtained from the RaisedHandUser data or marked as optional/undefined if not available.

Copilot uses AI. Check for mistakes.
&& subjectUserInAudio
&& !isMuted
&& !subjectUserVoice?.listenOnly
&& !isSubjectUserBot
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition for allowedToMuteAudio has been changed from && !isBreakout to && (type === 'participant' || type === 'raised-hand'). This means muting audio is now allowed in breakout rooms when it wasn't before. If the original intention was to prevent muting in breakout rooms for specific reasons, this change may introduce unintended behavior. Consider whether the breakout room check should be preserved: && !isBreakout && (type === 'participant' || type === 'raised-hand').

Suggested change
&& !isSubjectUserBot
&& !isSubjectUserBot
&& !isBreakout

Copilot uses AI. Check for mistakes.
export const useUserOperations = (pageId: string) => {
const intl = useIntl();
const toggleVoiceFunction = useToggleVoice();
const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState<boolean>(false);
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useUserOperations hook replaces the modal management system from useModalRegistration (which coordinates multiple modals with priorities) with a simple useState<boolean>. This change means that multiple instances of the confirmation modal (e.g., for different raised hand users) won't be properly coordinated through the modal controller system. Consider whether this simplified approach is appropriate, or if the useModalRegistration should be retained for proper modal management.

Copilot uses AI. Check for mistakes.
label={intl.formatMessage(intlMessages.lowerHandsLabel)}
color="default"
size="md"
onClick={() => raisedHands.forEach((u) => lowerUserHands(u.userId))}
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lowerUserHands function is defined locally but the logic for lowering all hands uses raisedHands.forEach((u) => lowerUserHands(u.userId)) which iterates and calls setRaiseHand for each user individually. This creates multiple GraphQL mutations in sequence. Consider whether a batch mutation would be more efficient, or if this approach might cause race conditions or performance issues with many raised hands.

Copilot uses AI. Check for mistakes.
&& !subjectUserVoice?.listenOnly
&& isMuted
&& (amISubjectUser || usersPolicies?.allowModsToUnmuteUsers)
&& !lockSettings?.disableMic
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition for allowedToUnmuteAudio has been changed from && !isBreakout to && (type === 'participant' || type === 'raised-hand'). This means unmuting audio is now allowed in breakout rooms when it wasn't before. If the original intention was to prevent unmuting in breakout rooms for specific reasons, this change may introduce unintended behavior. Consider whether the breakout room check should be preserved: && !isBreakout && (type === 'participant' || type === 'raised-hand').

Suggested change
&& !lockSettings?.disableMic
&& !lockSettings?.disableMic
&& !isBreakout

Copilot uses AI. Check for mistakes.
Comment on lines 29 to 30
if (raisedHands.length === 0) return null;

Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The early return if (raisedHands.length === 0) return null; is placed after setting up the state but before rendering. However, the parent component (RaisedHandsContainer) already checks for raisedHands.length === 0 at line 63 before calling this component. This makes the check here redundant and could be removed for cleaner code.

Suggested change
if (raisedHands.length === 0) return null;

Copilot uses AI. Check for mistakes.
&& isPrivateChatEnabled
&& !isSubjectUserBot
&& !isBreakout;
&& type === 'participant';
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition for allowedToChatPrivately has been changed from && !isBreakout to && type === 'participant'. This means that private chat will now be disabled in the raised-hand list even if the user is not in a breakout room. If the original intention was to disable private chat in breakout rooms, this change may break that functionality. Consider whether both conditions should be checked: && !isBreakout && type === 'participant'.

Suggested change
&& type === 'participant';
&& type === 'participant'
&& !isBreakout;

Copilot uses AI. Check for mistakes.
Comment on lines 57 to 60
const [getWriters] = useLazyQuery(CURRENT_PAGE_WRITERS_QUERY, {
variables: { pageId },
fetchPolicy: 'no-cache',
});
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getWriters query is initialized with variables: { pageId } in the useUserOperations hook, but the CURRENT_PAGE_WRITERS_QUERY in the original code was called with fetchPolicy: 'no-cache' and no initial variables. The pageId is passed as a parameter to useUserOperations but may not be the correct pageId when getWriters is actually invoked later in the toolbar options. Consider whether the pageId variable should be passed at call-time rather than initialization time, or if this will cause the query to use a stale pageId.

Suggested change
const [getWriters] = useLazyQuery(CURRENT_PAGE_WRITERS_QUERY, {
variables: { pageId },
fetchPolicy: 'no-cache',
});
const [getWritersRaw] = useLazyQuery(CURRENT_PAGE_WRITERS_QUERY, {
fetchPolicy: 'no-cache',
});
// Wrap getWriters to always use the latest pageId
const getWriters = () => getWritersRaw({ variables: { pageId } });

Copilot uses AI. Check for mistakes.
width: 1.25rem;
height: 1.25rem;
border-radius: 50%;
background-color: ${colorPrimary || '#0F70D2'};
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This use of variable 'colorPrimary' always evaluates to true.

Suggested change
background-color: ${colorPrimary || '#0F70D2'};
background-color: ${colorPrimary};

Copilot uses AI. Check for mistakes.
@github-actions
Copy link

This pull request has conflicts ☹
Please resolve those so we can review the pull request.
Thanks.

@germanocaumo germanocaumo merged commit 6c85bc6 into germanocaumo:merge-3.0.18-3.1 Dec 16, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants