Skip to content

Commit

Permalink
refactor(classroom): support displaying left on stage users (#1654)
Browse files Browse the repository at this point in the history
  • Loading branch information
crimx authored Aug 23, 2022
1 parent e1f2d80 commit d0115bd
Show file tree
Hide file tree
Showing 15 changed files with 164 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const makeUser = (): User => ({
isSpeak: faker.datatype.boolean(),
isRaiseHand: faker.datatype.boolean(),
avatar: "http://placekitten.com/64/64",
hasLeft: faker.datatype.boolean(),
});
Overview.args = {
generateAvatar: () => "http://placekitten.com/64/64",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ export const ChatUser = /* @__PURE__ */ observer<ChatUserProps>(function ChatUse
<span className="chat-user-name">{user.name}</span>
{ownerUUID === user.userUUID ? (
<span className="chat-user-status is-teacher">{t("teacher")}</span>
) : user.hasLeft ? (
<>
<span className="chat-user-status has-left">{t("has-left")}</span>
{(isCreator || isCurrentUser) && !disableEndSpeaking && (
<button
className="chat-user-ctl-btn is-speaking"
onClick={() => onEndSpeaking(user.userUUID)}
>
{t("end")}
</button>
)}
</>
) : user.isSpeak ? (
<>
<span className="chat-user-status is-speaking">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
&.is-hand-raising {
color: var(--primary);
}

&.has-left {
color: var(--danger);
}
}

.chat-user-ctl-btn {
Expand All @@ -59,6 +63,7 @@
border: 1px solid var(--danger);
}


&.is-hand-raising {
color: var(--grey-0);
background: var(--primary);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import placeholderSVG from "./icons/placeholder.svg";
import React, { FC } from "react";
import { useTranslate } from "@netless/flat-i18n";
import classnames from "classnames";
import { User } from "../../../types/user";

export interface VideoAvatarAbsentProps {
avatarUser?: User | null;
small?: boolean;
isAvatarUserCreator: boolean;
}
Expand Down
1 change: 1 addition & 0 deletions packages/flat-components/src/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export interface User {
isSpeak: boolean;
isRaiseHand: boolean;
avatar: string;
hasLeft?: boolean;
}
1 change: 1 addition & 0 deletions packages/flat-i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"messages": "Message list",
"raise-your-hand": "Raise hand",
"raised-hand": "(Hand raised)",
"has-left": "(Has left)",
"say-something": "Say something...",
"send": "send",
"teacher": "(Teacher)",
Expand Down
1 change: 1 addition & 0 deletions packages/flat-i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"during-the-presentation": "(发言中)",
"end": "结束",
"raised-hand": "(已举手)",
"has-left": "(已离开)",
"agree": "通过",
"me": "(我)",
"cancel-hand-raising": "取消举手",
Expand Down
16 changes: 8 additions & 8 deletions packages/flat-pages/src/BigClassPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,6 @@ export const BigClassPage = withClassroomStore<BigClassPageProps>(

const [isRealtimeSideOpen, openRealtimeSide] = useState(true);

const speakingJoiner =
classroomStore.users.speakingJoiners.length > 0
? classroomStore.users.speakingJoiners[0]
: undefined;

useEffect(() => {
if (classroomStore.isCreator && classroomStore.roomStatus === RoomStatus.Idle) {
void classroomStore.startClass();
Expand Down Expand Up @@ -185,12 +180,17 @@ export const BigClassPage = withClassroomStore<BigClassPageProps>(
updateDeviceState={classroomStore.updateDeviceState}
userUUID={classroomStore.userUUID}
/>
{speakingJoiner && (
{classroomStore.onStageUserUUIDs.length > 0 && (
<RTCAvatar
avatarUser={speakingJoiner}
avatarUser={classroomStore.firstOnStageUser}
isAvatarUserCreator={false}
isCreator={classroomStore.isCreator}
rtcAvatar={classroomStore.rtc.getAvatar(speakingJoiner.rtcUID)}
rtcAvatar={
classroomStore.firstOnStageUser &&
classroomStore.rtc.getAvatar(
classroomStore.firstOnStageUser.rtcUID,
)
}
updateDeviceState={classroomStore.updateDeviceState}
userUUID={classroomStore.userUUID}
/>
Expand Down
47 changes: 17 additions & 30 deletions packages/flat-pages/src/OneToOnePage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "./OneToOnePage.less";

import React, { useContext, useEffect, useState } from "react";
import React, { useContext, useState } from "react";
import { useTranslate } from "@netless/flat-i18n";
import { observer } from "mobx-react-lite";
import { message } from "antd";
Expand Down Expand Up @@ -29,7 +29,6 @@ import {
} from "../components/ExitRoomConfirm";
import { Whiteboard } from "../components/Whiteboard";
import { RoomStatusStoppedModal } from "../components/ClassRoom/RoomStatusStoppedModal";
import { RoomStatus } from "@netless/flat-server-api";
import { CloudStorageButton } from "../components/CloudStorageButton";
import { ShareScreen } from "../components/ShareScreen";
import { useLoginCheck } from "../utils/use-login-check";
Expand All @@ -51,25 +50,6 @@ export const OneToOnePage = withClassroomStore<OneToOnePageProps>(

const [isRealtimeSideOpen, openRealtimeSide] = useState(true);

const joiner = classroomStore.users.speakingJoiners[0] ?? null;

useEffect(() => {
if (classroomStore.isCreator && classroomStore.roomStatus === RoomStatus.Idle) {
void classroomStore.startClass();
}
}, [classroomStore]);

useEffect(() => {
if (
classroomStore.isCreator &&
classroomStore.users.joiners.length > 0 &&
classroomStore.users.speakingJoiners.length <= 0
) {
classroomStore.onStaging(classroomStore.users.joiners[0].userUUID, true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [classroomStore.users.joiners.length]);

return (
<div className="one-to-one-class-page-container">
<div className="one-to-one-realtime-container">
Expand Down Expand Up @@ -180,7 +160,7 @@ export const OneToOnePage = withClassroomStore<OneToOnePageProps>(
chatSlot={
<ChatPanel
classRoomStore={classroomStore}
disableEndSpeaking={true}
disableEndSpeaking={false}
maxSpeakingUsers={1}
></ChatPanel>
}
Expand All @@ -201,14 +181,21 @@ export const OneToOnePage = withClassroomStore<OneToOnePageProps>(
updateDeviceState={classroomStore.updateDeviceState}
userUUID={classroomStore.userUUID}
/>
<RTCAvatar
avatarUser={joiner}
isAvatarUserCreator={false}
isCreator={classroomStore.isCreator}
rtcAvatar={joiner && classroomStore.rtc.getAvatar(joiner.rtcUID)}
updateDeviceState={classroomStore.updateDeviceState}
userUUID={classroomStore.userUUID}
/>
{classroomStore.firstOnStageUser && (
<RTCAvatar
avatarUser={classroomStore.firstOnStageUser}
isAvatarUserCreator={false}
isCreator={classroomStore.isCreator}
rtcAvatar={
classroomStore.firstOnStageUser &&
classroomStore.rtc.getAvatar(
classroomStore.firstOnStageUser.rtcUID,
)
}
updateDeviceState={classroomStore.updateDeviceState}
userUUID={classroomStore.userUUID}
/>
)}
</div>
}
/>
Expand Down
14 changes: 7 additions & 7 deletions packages/flat-pages/src/SmallClassPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import ExitRoomConfirm, {
import { RoomStatusStoppedModal } from "../components/ClassRoom/RoomStatusStoppedModal";

import { RoomStatus } from "@netless/flat-server-api";
import { ClassModeType, User } from "@netless/flat-stores";

import { CloudStorageButton } from "../components/CloudStorageButton";
import { ShareScreen } from "../components/ShareScreen";
Expand Down Expand Up @@ -123,7 +122,7 @@ export const SmallClassPage = withClassroomStore<SmallClassPageProps>(
updateDeviceState={classroomStore.updateDeviceState}
userUUID={classroomStore.userUUID}
/>
{classroomStore.users.speakingJoiners.map(renderAvatar)}
{classroomStore.onStageUserUUIDs.map(renderAvatar)}
</div>
)}
</div>
Expand Down Expand Up @@ -242,22 +241,23 @@ export const SmallClassPage = withClassroomStore<SmallClassPageProps>(
);
}

function renderAvatar(user: User): React.ReactNode {
function renderAvatar(userUUID: string): React.ReactNode {
const user = classroomStore.users.cachedUsers.get(userUUID);
return (
<RTCAvatar
key={user.userUUID}
key={userUUID}
avatarUser={user}
isAvatarUserCreator={false}
isCreator={classroomStore.isCreator}
rtcAvatar={classroomStore.rtc.getAvatar(user.rtcUID)}
rtcAvatar={user && classroomStore.rtc.getAvatar(user.rtcUID)}
small={true}
updateDeviceState={(uid, camera, mic) => {
// Disallow toggling mic when not speak.
const _mic =
whiteboardStore.isWritable ||
!user ||
user.userUUID === classroomStore.ownerUUID ||
user.userUUID !== classroomStore.users.currentUser?.userUUID ||
classroomStore.classMode !== ClassModeType.Lecture
user.userUUID !== classroomStore.users.currentUser?.userUUID
? mic
: user.mic;
classroomStore.updateDeviceState(uid, camera, _mic);
Expand Down
15 changes: 9 additions & 6 deletions packages/flat-pages/src/components/ChatPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { ChatPanel as ChatPanelImpl, useComputed } from "flat-components";
import { ClassroomStore } from "@netless/flat-stores";
import { ClassroomStore, User } from "@netless/flat-stores";
import { generateAvatar } from "../../utils/generate-avatar";

export interface ChatPanelProps {
Expand All @@ -19,10 +19,13 @@ export const ChatPanel = observer<ChatPanelProps>(function ChatPanel({
maxSpeakingUsers = 1,
}) {
const users = useComputed(() => {
const { creator, speakingJoiners, handRaisingJoiners, otherJoiners } = classRoomStore.users;
const onStageUsers = classRoomStore.onStageUserUUIDs
.map(userUUID => classRoomStore.users.cachedUsers.get(userUUID))
.filter((user): user is User => !!user);
const { creator, handRaisingJoiners, otherJoiners } = classRoomStore.users;
return creator
? [...speakingJoiners, ...handRaisingJoiners, creator, ...otherJoiners]
: [...speakingJoiners, ...handRaisingJoiners, ...otherJoiners];
? [...onStageUsers, ...handRaisingJoiners, creator, ...otherJoiners]
: [...onStageUsers, ...handRaisingJoiners, ...otherJoiners];
}).get();

const handHandRaising = classRoomStore.users.handRaisingJoiners.length > 0;
Expand All @@ -43,10 +46,10 @@ export const ChatPanel = observer<ChatPanelProps>(function ChatPanel({
userUUID={classRoomStore.userUUID}
users={users}
withAcceptHands={
handHandRaising && classRoomStore.users.speakingJoiners.length < maxSpeakingUsers
handHandRaising && classRoomStore.onStageUserUUIDs.length < maxSpeakingUsers
}
onAcceptRaiseHand={(userUUID: string) => {
if (classRoomStore.users.speakingJoiners.length < maxSpeakingUsers) {
if (classRoomStore.onStageUserUUIDs.length < maxSpeakingUsers) {
classRoomStore.acceptRaiseHand(userUUID);
}
}}
Expand Down
63 changes: 35 additions & 28 deletions packages/flat-pages/src/components/RTCAvatar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { FC } from "react";
import { observer } from "mobx-react-lite";
import {
VideoAvatar,
VideoAvatarAbsent,
Expand All @@ -10,31 +11,37 @@ export type RTCAvatarProps = Omit<VideoAvatarProps, "getVolumeLevel" | "avatarUs
VideoAvatarAbsentProps &
AvatarCanvasProps;

export const RTCAvatar: FC<RTCAvatarProps> = ({
userUUID,
avatarUser,
rtcAvatar,
isAvatarUserCreator,
small,
isCreator,
updateDeviceState,
}) => {
return avatarUser ? (
<AvatarCanvas avatarUser={avatarUser} rtcAvatar={rtcAvatar}>
{(getVolumeLevel, canvas) => (
<VideoAvatar
avatarUser={avatarUser}
getVolumeLevel={getVolumeLevel}
isCreator={isCreator}
small={small}
updateDeviceState={updateDeviceState}
userUUID={userUUID}
>
{canvas}
</VideoAvatar>
)}
</AvatarCanvas>
) : (
<VideoAvatarAbsent isAvatarUserCreator={isAvatarUserCreator} small={small} />
);
};
export const RTCAvatar: FC<RTCAvatarProps> = /* @__PURE__ */ observer<RTCAvatarProps>(
function RTCAvatar({
userUUID,
avatarUser,
rtcAvatar,
isAvatarUserCreator,
small,
isCreator,
updateDeviceState,
}) {
return avatarUser && !avatarUser.hasLeft ? (
<AvatarCanvas avatarUser={avatarUser} rtcAvatar={rtcAvatar}>
{(getVolumeLevel, canvas) => (
<VideoAvatar
avatarUser={avatarUser}
getVolumeLevel={getVolumeLevel}
isCreator={isCreator}
small={small}
updateDeviceState={updateDeviceState}
userUUID={userUUID}
>
{canvas}
</VideoAvatar>
)}
</AvatarCanvas>
) : (
<VideoAvatarAbsent
avatarUser={avatarUser}
isAvatarUserCreator={isAvatarUserCreator}
small={small}
/>
);
},
);
Loading

0 comments on commit d0115bd

Please sign in to comment.