Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(classroom): support displaying left on stage users #1654

Merged
merged 1 commit into from
Aug 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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