Merge pull request #10458 from pedrobmarin/avatar-image

Support for avatar images
This commit is contained in:
Anton Georgiev 2020-09-21 08:22:04 -04:00 committed by GitHub
commit d5450af5df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 89 additions and 23 deletions

View File

@ -56,7 +56,7 @@ trait RegisterUserReqMsgHdlr {
val g = GuestApprovedVO(regUser.id, GuestStatus.ALLOW)
UsersApp.approveOrRejectGuest(liveMeeting, outGW, g, SystemUser.ID)
case GuestStatus.WAIT =>
val guest = GuestWaiting(regUser.id, regUser.name, regUser.role, regUser.guest, regUser.authed)
val guest = GuestWaiting(regUser.id, regUser.name, regUser.role, regUser.guest, regUser.avatarURL, regUser.authed)
addGuestToWaitingForApproval(guest, liveMeeting.guestsWaiting)
notifyModeratorsOfGuestWaiting(Vector(guest), liveMeeting.users2x, liveMeeting.props.meetingProp.intId)
case GuestStatus.DENY =>

View File

@ -51,7 +51,7 @@ class GuestsWaiting {
def setGuestPolicy(policy: GuestPolicy) = guestPolicy = policy
}
case class GuestWaiting(intId: String, name: String, role: String, guest: Boolean, authenticated: Boolean)
case class GuestWaiting(intId: String, name: String, role: String, guest: Boolean, avatar: String, authenticated: Boolean)
case class GuestPolicy(policy: String, setBy: String)
object GuestPolicyType {

View File

@ -44,7 +44,7 @@ object MsgBuilder {
val envelope = BbbCoreEnvelope(GetGuestsWaitingApprovalRespMsg.NAME, routing)
val header = BbbClientMsgHeader(GetGuestsWaitingApprovalRespMsg.NAME, meetingId, userId)
val guestsWaiting = guests.map(g => GuestWaitingVO(g.intId, g.name, g.role, g.guest, g.authenticated))
val guestsWaiting = guests.map(g => GuestWaitingVO(g.intId, g.name, g.role, g.guest, g.avatar, g.authenticated))
val body = GetGuestsWaitingApprovalRespMsgBody(guestsWaiting)
val event = GetGuestsWaitingApprovalRespMsg(header, body)
@ -56,7 +56,7 @@ object MsgBuilder {
val envelope = BbbCoreEnvelope(GuestsWaitingForApprovalEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(GuestsWaitingForApprovalEvtMsg.NAME, meetingId, userId)
val guestsWaiting = guests.map(g => GuestWaitingVO(g.intId, g.name, g.role, g.guest, g.authenticated))
val guestsWaiting = guests.map(g => GuestWaitingVO(g.intId, g.name, g.role, g.guest, g.avatar, g.authenticated))
val body = GuestsWaitingForApprovalEvtMsgBody(guestsWaiting)
val event = GuestsWaitingForApprovalEvtMsg(header, body)

View File

@ -20,13 +20,13 @@ trait FakeTestData {
val guest1 = createUserVoiceAndCam(liveMeeting, Roles.VIEWER_ROLE, guest = true, authed = true, CallingWith.WEBRTC, muted = false,
talking = false, listenOnly = false)
Users2x.add(liveMeeting.users2x, guest1)
val guestWait1 = GuestWaiting(guest1.intId, guest1.name, guest1.role, guest1.guest, guest1.authed)
val guestWait1 = GuestWaiting(guest1.intId, guest1.name, guest1.role, guest1.guest, "", guest1.authed)
GuestsWaiting.add(liveMeeting.guestsWaiting, guestWait1)
val guest2 = createUserVoiceAndCam(liveMeeting, Roles.VIEWER_ROLE, guest = true, authed = true, CallingWith.FLASH, muted = false,
talking = false, listenOnly = false)
Users2x.add(liveMeeting.users2x, guest2)
val guestWait2 = GuestWaiting(guest2.intId, guest2.name, guest2.role, guest2.guest, guest2.authed)
val guestWait2 = GuestWaiting(guest2.intId, guest2.name, guest2.role, guest2.guest, "", guest2.authed)
GuestsWaiting.add(liveMeeting.guestsWaiting, guestWait2)
val vu1 = FakeUserGenerator.createFakeVoiceOnlyUser(CallingWith.PHONE, muted = false, talking = false, listenOnly = false)

View File

@ -19,7 +19,7 @@ case class GetGuestsWaitingApprovalRespMsg(
body: GetGuestsWaitingApprovalRespMsgBody
) extends BbbCoreMsg
case class GetGuestsWaitingApprovalRespMsgBody(guests: Vector[GuestWaitingVO])
case class GuestWaitingVO(intId: String, name: String, role: String, guest: Boolean, authenticated: Boolean)
case class GuestWaitingVO(intId: String, name: String, role: String, guest: Boolean, avatar: String, authenticated: Boolean)
/**
* Message sent to client for list of guest waiting for approval. This is sent when

View File

@ -923,7 +923,7 @@ public class MeetingService implements MessageListener {
} else {
if (message.userId.startsWith("v_")) {
// A dial-in user joined the meeting. Dial-in users by convention has userId that starts with "v_".
User vuser = new User(message.userId, message.userId, message.name, "DIAL-IN-USER", "no-avatar-url",
User vuser = new User(message.userId, message.userId, message.name, "DIAL-IN-USER", "",
true, GuestPolicy.ALLOW, "DIAL-IN");
vuser.setVoiceJoined(true);
m.userJoined(vuser);

View File

@ -78,6 +78,7 @@ public class ParamsProcessorUtil {
private Boolean moderatorsJoinViaHTML5Client;
private Boolean attendeesJoinViaHTML5Client;
private Boolean allowRequestsWithoutSession;
private Boolean useDefaultAvatar = false;
private String defaultAvatarURL;
private String defaultConfigURL;
private String defaultGuestPolicy;
@ -464,6 +465,8 @@ public class ParamsProcessorUtil {
externalMeetingId = externalHash + "-" + timeStamp;
}
String avatarURL = useDefaultAvatar ? defaultAvatarURL : "";
// Create the meeting with all passed in parameters.
Meeting meeting = new Meeting.Builder(externalMeetingId,
internalMeetingId, createTime).withName(meetingName)
@ -474,7 +477,7 @@ public class ParamsProcessorUtil {
.withBannerText(bannerText).withBannerColor(bannerColor)
.withTelVoice(telVoice).withWebVoice(webVoice)
.withDialNumber(dialNumber)
.withDefaultAvatarURL(defaultAvatarURL)
.withDefaultAvatarURL(avatarURL)
.withAutoStartRecording(autoStartRec)
.withAllowStartStopRecording(allowStartStoptRec)
.withWebcamsOnlyForModerator(webcamsOnlyForMod)
@ -952,6 +955,10 @@ public class ParamsProcessorUtil {
this.webcamsOnlyForModerator = webcamsOnlyForModerator;
}
public void setUseDefaultAvatar(Boolean value) {
this.useDefaultAvatar = value;
}
public void setdefaultAvatarURL(String url) {
this.defaultAvatarURL = url;
}

View File

@ -126,6 +126,7 @@ class MessageListItem extends Component {
className={styles.avatar}
color={user.color}
moderator={user.isModerator}
avatar={user.avatar}
>
{user.name.toLowerCase().slice(0, 2)}
</UserAvatar>

View File

@ -50,13 +50,18 @@ const mapGroupMessage = (message) => {
const sender = Users.findOne({ userId: message.sender },
{
fields: {
color: 1, role: 1, name: 1, connectionStatus: 1,
color: 1,
role: 1,
name: 1,
avatar: 1,
connectionStatus: 1,
},
});
const {
color,
role,
name,
avatar,
connectionStatus,
} = sender;
@ -64,6 +69,7 @@ const mapGroupMessage = (message) => {
color,
isModerator: role === ROLE_MODERATOR,
name,
avatar,
isOnline: connectionStatus === CONNECTION_STATUS_ONLINE,
};

View File

@ -84,8 +84,9 @@ class ConnectionStatusComponent extends PureComponent {
<div className={styles.left}>
<div className={styles.avatar}>
<UserAvatar
className={styles.icon}
className={cx({ [styles.initials]: conn.avatar.length === 0 })}
you={conn.you}
avatar={conn.avatar}
moderator={conn.moderator}
color={conn.color}
>

View File

@ -82,7 +82,7 @@
justify-content: center;
align-items: center;
.icon {
.initials {
min-width: 2.25rem;
height: 2.25rem;
}

View File

@ -87,6 +87,7 @@ const getConnectionStatus = () => {
userId: 1,
name: 1,
role: 1,
avatar: 1,
color: 1,
connectionStatus: 1,
},
@ -96,6 +97,7 @@ const getConnectionStatus = () => {
userId,
name,
role,
avatar,
color,
connectionStatus: userStatus,
} = user;
@ -105,6 +107,7 @@ const getConnectionStatus = () => {
if (status) {
result.push({
name,
avatar,
offline: userStatus === 'offline',
you: Auth.userID === userId,
moderator: role === ROLE_MODERATOR,

View File

@ -14,6 +14,8 @@ const propTypes = {
voice: PropTypes.bool,
noVoice: PropTypes.bool,
color: PropTypes.string,
emoji: PropTypes.bool,
avatar: PropTypes.string,
className: PropTypes.string,
};
@ -26,6 +28,8 @@ const defaultProps = {
voice: false,
noVoice: false,
color: '#000',
emoji: false,
avatar: '',
className: null,
};
@ -38,6 +42,8 @@ const UserAvatar = ({
listenOnly,
color,
voice,
emoji,
avatar,
noVoice,
className,
}) => (
@ -60,14 +66,27 @@ const UserAvatar = ({
>
<div className={cx({
[styles.talking]: (talking && !muted),
[styles.talking]: (talking && !muted && avatar.length === 0),
})}
/>
<div className={styles.content}>
{children}
</div>
{avatar.length !== 0 && !emoji
? (
<div className={styles.image}>
<img
className={cx(styles.img, {
[styles.circle]: !moderator,
[styles.square]: moderator,
})}
src={avatar}
/>
</div>
) : (
<div className={styles.content}>
{children}
</div>
)
}
</div>
);

View File

@ -13,7 +13,8 @@
.avatar {
position: relative;
padding-bottom: 2rem;
height: 2.25rem;
min-width: 2.25rem;
border-radius: 50%;
text-align: center;
font-size: .85rem;
@ -166,6 +167,25 @@
@include indicatorStyles();
}
.image {
display: flex;
height: 2rem;
width: 2rem;
.img {
object-fit: cover;
overflow: hidden;
}
.circle {
border-radius: 50%;
}
.square {
border-radius: 3px;
}
}
.content {
color: var(--user-avatar-text);
top: 50%;

View File

@ -12,11 +12,14 @@ const defaultProps = {
};
const ChatAvatar = (props) => {
const { color, name, isModerator } = props;
const {
color, name, avatar, isModerator,
} = props;
return (
<UserAvatar
moderator={isModerator}
avatar={avatar}
color={color}
>
{name.toLowerCase().slice(0, 2)}

View File

@ -96,6 +96,7 @@ const ChatListItem = (props) => {
<ChatAvatar
isModerator={chat.isModerator}
color={chat.color}
avatar={chat.avatar}
name={chat.name.toLowerCase().slice(0, 2)}
/>
)}

View File

@ -256,6 +256,7 @@ const getActiveChats = (chatID) => {
const activeChat = op;
activeChat.unreadCounter = UnreadMessages.count(op.userId);
activeChat.name = op.name;
activeChat.avatar = op.avatar;
activeChat.isModerator = op.role === ROLE_MODERATOR;
activeChat.lastActivity = idsWithTimeStamp[`${op.userId}`];
return activeChat;

View File

@ -538,6 +538,8 @@ class UserDropdown extends PureComponent {
voice={voiceUser.isVoiceUser}
noVoice={!voiceUser.isVoiceUser}
color={user.color}
emoji={user.emoji !== 'none'}
avatar={user.avatar}
>
{
userInBreakout

View File

@ -66,13 +66,14 @@ const getNameInitials = (name) => {
return nameInitials.replace(/^\w/, c => c.toUpperCase());
}
const renderGuestUserItem = (name, color, handleAccept, handleDeny, role, sequence, userId, intl) => (
const renderGuestUserItem = (name, color, handleAccept, handleDeny, role, sequence, userId, avatar, intl) => (
<div key={`userlist-item-${userId}`} className={styles.listItem}>
<div key={`user-content-container-${userId}`} className={styles.userContentContainer}>
<div key={`user-avatar-container-${userId}`} className={styles.userAvatar}>
<UserAvatar
key={`user-avatar-${userId}`}
moderator={role === 'MODERATOR'}
avatar={avatar}
color={color}
>
{getNameInitials(name)}
@ -123,6 +124,7 @@ const renderPendingUsers = (message, usersArray, action, intl) => {
user.role,
idx + 1,
user.intId,
user.avatar,
intl,
))}
</div>

View File

@ -254,9 +254,8 @@ html5ClientUrl=${bigbluebutton.web.serverURL}/html5client/join
# The url for where the guest will poll if approved to join or not.
defaultGuestWaitURL=${bigbluebutton.web.serverURL}/client/guest-wait.html
# The default avatar image to display if nothing is passed on the JOIN API (avatarURL)
# call. This avatar is displayed if the user isn't sharing the webcam and
# the option (displayAvatar) is enabled in config.xml
# The default avatar image to display.
useDefaultAvatar=false
defaultAvatarURL=${bigbluebutton.web.serverURL}/client/avatar.png
# The URL of the default configuration

View File

@ -133,6 +133,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<property name="autoStartRecording" value="${autoStartRecording}"/>
<property name="allowStartStopRecording" value="${allowStartStopRecording}"/>
<property name="webcamsOnlyForModerator" value="${webcamsOnlyForModerator}"/>
<property name="useDefaultAvatar" value="${useDefaultAvatar}"/>
<property name="defaultAvatarURL" value="${defaultAvatarURL}"/>
<property name="defaultConfigURL" value="${defaultConfigURL}"/>
<property name="defaultGuestPolicy" value="${defaultGuestPolicy}"/>