Classroom and Proctor SDK

Updated

Easily update the look and feel of your classrooms.

This page explains the way UI components work in Classroom and Proctor SDK.

Introduction to UI components

The cloud classroom scene is included in FcrUIScene. If you need to integrate the cloud classroom scene UI, please refer to FcrUIScene SDK.

The UI components in Flexible Classroom can be divided into three types: function components, business components, and scenario components.

Function components

Function components are the most basic UI components in Flexible Classroom and are not bound to any business logic. A function component maintains the internal state and logic of a function, such as Button, Modal, Select, or Tree.

The Agora Classroom Education Scenario functional components are located in packages/agora-classroom-sdk/src/ui-kit, and the Agora Classroom Proctor Scenario components are in the packages/agora-proctor-sdk/src/ui-kit folder. The technologies used are react, ts, and storybook. Each functional component folder contains the following files:

  • .tsx: Implements the functionality of the component.
  • .css: Implements the style of the component.
  • .stories.tsx: Enables previewing and debugging the component in Storybook. Developers can run the project through the yarn dev:ui-kit:classroom or npm run dev:ui-kit:classroom command and preview the function components in Storybook.

The following table lists the function components used in Flexible Classroom:

FolderFunction components
/buttonThe button component.
/cardThe card component.
/checkboxThe checkbox component.
/inputThe input box.
/layoutThe layout component helps with the overall layout of a web page.
/loadingThe loading component provides the state of joining a classroom or uploading a file.
/modalThe modal dialog box is used for user interaction that does not interrupt the current operation.
/paginationThe pagination component is used for displaying long lists. It loads only one page of the data.
/placeholderThe placeholder for videos or files.
/popoverThe pop-up box is used to display more content when the mouse clicks or moves in.
/radioThe radio buttons allow users to select one option from a set.
/root-boxThe root container wraps page elements.
/rosterThe roster is used to display the student list and allows teachers to invite students to address the class, send rewards to students, or kick students out of the classroom.
/selectThe drop-down menu.
/sound-playerThe component for playing audio files.
/svg-imgSVG images.
/tableThe table displays rows of data.
/tabsThe component for switching tabs.
/toastThe component for global prompts.
/toolbarThe toolbar displays the teaching tools of teachers and students.
/tooltipThe pop-up box for simple text tips.
/treeThe tree component is used to show tree-structured data.
/volumeThe component for displaying the volume.

Business components

Business components are the UI components bound to business logic in Flexible Classroom. Most of the business components are composed of multiple function components. Depending on the observable objects and functions from the UI Store, the UI of business components can be automatically changed as the data updates. Taking the hand-raising feature as an example, this business component provides a button for users to raise their hands and ask for permission to speak, and then displays the list of users who have raised their hands.

You can find the business components in packages/agora-classroom-sdk/src/ui-kit/capabilities/containers.

The business components for the Agora Classroom Education Scenario are located in packages/agora-classroom-sdk/src/infra/capabilities/containers, and the business components for Agora Classroom Proctor Scenario are in packages/agora-proctor-sdk/src/infra/capabilities/containers

The following table lists the business components used in Flexible Classroom:

Education Scene

FolderBusiness components
/asideThe video and chat box containers on the right side of a large and small classroom.
/awardThe reward component that implements the logic of teachers issuing rewards to students. When students receive rewards, they are displayed in the video box.
/cloud-driverThe cloud drive component implements businesses such as uploading or deleting files.
/device-settingThe component for device settings, such as displaying the list of cameras, microphones, and speakers.
/dialogPop-up windows during the class.
/hand-upThe component for the hand-raising feature.
/loadingThe loading component.
/navThe navigation component for businesses such as displaying the network status and class status.
/pretestThe component for the device test before joining the classroom. Includes functions such as obtaining device list information and switching devices.
/root-boxThe root container. Used to maintain the 16
aspect ratio of the class, with areas outside the ratio showing a black background.
/rosterThe roster component implements businesses such as checking the student information, processing requests for going onto the stage, and issuing rewards.
/scene-switchThe component for switching between scenes.
/scenes-controllerThe component for controlling whiteboard scenes, such as adding or deleting whiteboard pages.
/screen-shareThe component for the screen sharing feature. Responsible for rendering the shared screen and the screen sharing control bar.
/streamThe component for rendering audio and video streams. Handles audio and video rendering of each class, a small class carousel component.
/stream-windowThe draggable window container component. Handles the dragging and arrangement logic of the video window.
/toastToast prompt component.
/toolbarThe toolbar for the teaching tools of the teachers and students.
/widgetThe Widget container component that is responsible for handling logic such as rendering and movement trajectory synchronization of custom Widgets.
/camera-previewThe local video screen displayed in the lower right corner when the video wall function is turned on.

Proctor Scene

FolderBusiness components
/dialogPop-up windows during the class.
/pretestThe component for the device test before joining the classroom. Includes functions such as obtaining device list information and switching devices.
/root-boxThe root container.
/streamThe component for rendering audio and video streams.
/toastToast prompt component.
/widgetThe Widget component handles logic such as widget rendering and loading.

Scenario components

A scenario components is composed of multiple business components. Flexible classroom supports preset scenarios. Scenario components are located in packages/agora-classroom-sdk/src/infra/capabilities/scenarios for the Agora Classroom Education Scenario and in packages/agora-proctor-sdk/src/infra/capabilities/scenarios for the Agora Classroom Proctor Scenario. If you want to change the layout of a certain scene, just find the corresponding scene component and modify it.

Education Scene

FolderScenario components
/1v1One-to-one interactive teaching scene
/big-classInteractive live broadcast of large class scene
/big-class-mobileInteractive live broadcast, large class scene for web and mobile terminals
/mid-classOnline interactive small class scene

Proctor Scene

FolderScenario components
proctorProctor teacher scene
/examineeInvigilation student scene

Relationship between the three types of UI components

The following figure illustrates the relationship between the three types of UI components:

Custom functional components

Add a functional component

To add a new functional component and use it in Flexible Classroom, follow these steps:

  1. Create a folder in the packages/agora-classroom-sdk/src/ui-kit/components directory for the new functional component that you want to add. This folder should contain the following three files:
    • index.tsx: Implements the functionality of the UI component.
    • index.css: Implements the style of the UI component.
    • index.stories.tsx: Enables previewing and debugging the UI component in Storybook.
  2. After adding the functional component, export this component in packages/agora-classroom-sdk/src/ui-kit/components/index.ts:

Then you can import this component into your project.

The following example shows how to add a functional component agora-demo:

  1. Create a new folder packages/agora-classroom-sdk/src/ui-kit/components under the directory, containing the corresponding files .agora-demo, index.tsx, index.css, and index.stories.tsx:

    // index.css
    .agora-demo {
        color: red
    }
    
    // index.tsx
    import React from 'react'
    import './index.css'
    
    export const AgoraDemo = () => {
      return (
        <div className="agora-demo">AgoraDemo</div>
      )
    }
    
    // index.stories.tsx
    import React from 'react';
    import { Meta } from '@storybook/react';
    import { AgoraDemo } from './index';
    
    const meta: Meta = {
        title: 'Components/AgoraDemo',
        component: AgoraDemo,
    };
    
    export default meta;
    
    export const Docs = () => (
        <AgoraDemo />
    )
  2. Add export * from './agora-demo'; to packages/agora-classroom-sdk/src/ui-kit/components/index.ts. You can see this function component in Storybook, as follows:

Edit an existing functional component

If you want to modify the function and style of an existing functional component, just find component folder and edit the code to suit your needs. The following examples are provided for your reference.

Change the placeholder color of the input component

To change the color of the placeholder text in the input component, edit the packages/agora-classroom-sdk/src/ui-kit/components/input/index.css file as follows:

Before

.fcr-input-wrapper input::-webkit-input-placeholder {
  color: #999;
}

After

.fcr-input-wrapper input::-webkit-input-placeholder {
  color: #ff6600;
}

Customize business components

Add a business component

To add a new business component and use it in Flexible Classroom, create a folder in packages/agora-classroom-sdk/src/infra/capabilities/containers. The folder should contain the following files:

  • index.tsx: Combines functional components and implements the business logic.
  • index.css: Implements the style of the business component.

After adding the business component, you can directly import this business component and run the project to see how it looks in Flexible Classroom.

The following example shows how to add a business component that displays the class and network status:

// index.css
.agora-demo {
  width: 50%;
  height: 50%;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
  border: 1px solid black;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  z-index: 99999999;
}
.agora-demo-title {
  color: red;
}

// index.tsx
import React from 'react';
import { observer } from 'mobx-react';
import { useStore } from '@classroom/infra/hooks/ui-store';
import './index.css';

export default observer(function AgoraDemo() {
  const { navigationBarUIStore } = useStore();
  const { classStatusText, networkQualityLabel, delay, packetLoss } = navigationBarUIStore;
  return (
    <div className="agora-demo">
      <h1 className="agora-demo-title">This is our new business component.</h1>
      <h2>This component is used to display the network status and class status.</h2>
      <div>
        Network Status: {networkQualityLabel} Network Delay: {delay} Packet Loss Rate: {packetLoss}
      </div>
      <div>Class Status: {classStatusText}</div>
    </div>
  );
});

// packages/agora-classroom-sdk/src/infra/capabilities/scenarios/mid-class/index.tsx
// Introduce this component in the small class scene
import classnames from 'classnames';
import { Layout } from '@classroom/ui-kit/components/layout';
import { DialogContainer } from '@classroom/infra/capabilities/containers/dialog';
import { HandsUpContainer } from '@classroom/infra/capabilities/containers/hand-up';
import { LoadingContainer } from '@classroom/infra/capabilities/containers/loading';
import { NavigationBar } from '@classroom/infra/capabilities/containers/nav';
import { FixedAspectRatioRootBox } from '@classroom/infra/capabilities/containers/root-box';
import { SceneSwitch } from '@classroom/infra/capabilities/containers/scene-switch';
import { RoomMidStreamsContainer } from '@classroom/infra/capabilities/containers/stream/room-mid-player';
import { ToastContainer } from '@classroom/infra/capabilities/containers/toast';
import { Award } from '@classroom/infra/capabilities/containers/award';
import Room from '../room';
import { useStore } from '@classroom/infra/hooks/ui-store';
import { Float } from '@classroom/ui-kit';
import { RemoteControlContainer } from '@classroom/infra/capabilities/containers/remote-control';
import { ScenesController } from '@classroom/infra/capabilities/containers/scenes-controller';
import { ScreenShareContainer } from '@classroom/infra/capabilities/containers/screen-share';
import { WhiteboardToolbar } from '@classroom/infra/capabilities/containers/toolbar';
import { WidgetContainer } from '@classroom/infra/capabilities/containers/widget';
import { Chat, Watermark, Whiteboard } from '@classroom/infra/capabilities/containers/widget/slots';
import { StreamWindowsContainer } from '@classroom/infra/capabilities/containers/stream-windows-container';
import { RemoteControlToolbar } from '@classroom/infra/capabilities/containers/remote-control/toolbar';
import AgoraDemo from '@classroom/infra/capabilities/containers/agora-demo';

export const MidClassScenario = () => {
  // Scene layout
  const layoutCls = classnames('edu-room', 'mid-class-room');
  const { shareUIStore } = useStore();

  return (
    <Room>
      {/* Here is the new business component */}
      <AgoraDemo />
      <FixedAspectRatioRootBox trackMargin={{ top: shareUIStore.navHeight }}>
        <SceneSwitch>
          <Layout className={layoutCls} direction="col">
            <NavigationBar />
            <Layout
              className="flex-grow items-stretch relative justify-center fcr-room-bg"
              direction="col">
              <RoomMidStreamsContainer />
              <Whiteboard />
              <ScreenShareContainer />
              <RemoteControlContainer />
              <StreamWindowsContainer />
            </Layout>
            <RemoteControlToolbar />
            <WhiteboardToolbar />
            <ScenesController />
            <Float bottom={15} right={10} align="flex-end" gap={2}>
              <HandsUpContainer />
              <Chat />
            </Float>
            <DialogContainer />
            <LoadingContainer />
          </Layout>
          <WidgetContainer />
          <ToastContainer />
          <Award />
          <Watermark />
        </SceneSwitch>
      </FixedAspectRatioRootBox>
    </Room>
  );
};

The effect of this business component in Flexible Classroom is as follows:

Edit an existing business component

If you want to change the function and style of an existing business component, just find the component folder and edit the code to suit your needs. The following examples are provided for your reference.

Display the number of cameras that are available in the device settings pop-up window

Open the file packages/agora-classroom-sdk/src/infra/capabilities/containers/pretest/pretest-video.tsx and modify it as follows:

const VideoDeviceList = observer(() => {
  const {
    pretestUIStore: { setCameraDevice, currentCameraDeviceId, cameraDevicesList },
  } = useStore();
  const t = useI18n();
  return (
    <VideoDeviceListPanel>

      <div className="-mt-10">{`${t('device.camera')} Number of devices: ${
        cameraDevicesList.length - 1
      }`}</div>
      <Field
        label=""
        type="select"
        value={currentCameraDeviceId}
        options={cameraDevicesList.map((value) => ({
          text: value.label,
          value: value.value,
        }))}
        onChange={(value) => {
          setCameraDevice(value);
        }}
      />
    </VideoDeviceListPanel>
  );

Before

After

Change the scenario layout

To change the layout of a scenario, just edit the code of the scenario component.

Move the position of the video and chat area

The following example demonstrates how to move the position of the video and chat areas from the right side of the screen to the left. This is a cross-component adjustment. Therefore, you need to edit the parent container of these two components, which is the one-to-one interactive teaching scene container in packages/agora-classroom-sdk/src/infra/capabilities/scenarios/1v1/index.tsx.

Before

import { useStore } from '@classroom/infra/hooks/ui-store';
import classnames from 'classnames';
import { Layout } from '@classroom/ui-kit/components/layout';
import { DialogContainer } from '@classroom/infra/capabilities/containers/dialog';
import { LoadingContainer } from '@classroom/infra/capabilities/containers/loading';
import { NavigationBar } from '@classroom/infra/capabilities/containers/nav';
import { FixedAspectRatioRootBox } from '@classroom/infra/capabilities/containers/root-box/fixed-aspect-ratio';
import { Room1v1StreamsContainer } from '@classroom/infra/capabilities/containers/stream/room-1v1-player';
import { ToastContainer } from '@classroom/infra/capabilities/containers/toast';
import { RemoteControlContainer } from '../../containers/remote-control';
import { SceneSwitch } from '../../containers/scene-switch';
import { ScenesController } from '../../containers/scenes-controller';
import { ScreenShareContainer } from '../../containers/screen-share';
import { StreamWindowsContainer } from '../../containers/stream-window';
import { WhiteboardToolbar } from '../../containers/toolbar';
import { WidgetContainer } from '../../containers/widget';
import { Chat, Watermark, Whiteboard } from '../../containers/widget/slots';
import { OneToOneClassAside as Aside } from '@classroom/infra/capabilities/containers/aside';
import Room from '../room';
import { RemoteControlToolbar } from '../../containers/remote-control/toolbar';

  const layoutCls = classnames('edu-room', 'one-on-one-class-room');
  const { shareUIStore } = useStore();
  return (
    <Room>
      <FixedAspectRatioRootBox trackMargin={{ top: shareUIStore.navHeight }}>
        <SceneSwitch>
          <Layout className={layoutCls} direction="col">
            <NavigationBar />
            <Layout className="flex-grow items-stretch fcr-room-bg h-full">
              <Layout
                className="flex-grow items-stretch relative"
                direction="col"
                style={{ paddingTop: 2 }}>
                <Whiteboard />
                <ScreenShareContainer />
                <WhiteboardToolbar />
                <ScenesController />
                <RemoteControlContainer />
                <StreamWindowsContainer />
                <RemoteControlToolbar />
              </Layout>
              <Aside>
                <Room1v1StreamsContainer />
                <Chat />
              </Aside>
            </Layout>
            <DialogContainer />
            <LoadingContainer />
          </Layout>
          <WidgetContainer />
          <ToastContainer />
          <Watermark />
        </SceneSwitch>
      </FixedAspectRatioRootBox>
    </Room>
  );
};

After

import { useStore } from '@classroom/infra/hooks/ui-store';
import classnames from 'classnames';
import { Layout } from '@classroom/ui-kit/components/layout';
import { DialogContainer } from '@classroom/infra/capabilities/containers/dialog';
import { LoadingContainer } from '@classroom/infra/capabilities/containers/loading';
import { NavigationBar } from '@classroom/infra/capabilities/containers/nav';
import { FixedAspectRatioRootBox } from '@classroom/infra/capabilities/containers/root-box/fixed-aspect-ratio';
import { Room1v1StreamsContainer } from '@classroom/infra/capabilities/containers/stream/room-1v1-player';
import { ToastContainer } from '@classroom/infra/capabilities/containers/toast';
import { RemoteControlContainer } from '../../containers/remote-control';
import { SceneSwitch } from '../../containers/scene-switch';
import { ScenesController } from '../../containers/scenes-controller';
import { ScreenShareContainer } from '../../containers/screen-share';
import { StreamWindowsContainer } from '../../containers/stream-window';
import { WhiteboardToolbar } from '../../containers/toolbar';
import { WidgetContainer } from '../../containers/widget';
import { Chat, Watermark, Whiteboard } from '../../containers/widget/slots';
import { OneToOneClassAside as Aside } from '@classroom/infra/capabilities/containers/aside';
import Room from '../room';
import { RemoteControlToolbar } from '../../containers/remote-control/toolbar';

  const layoutCls = classnames('edu-room', 'one-on-one-class-room');
  const { shareUIStore } = useStore();
  return (
    <Room>
      <FixedAspectRatioRootBox trackMargin={{ top: shareUIStore.navHeight }}>
        <SceneSwitch>
          <Layout className={layoutCls} direction="col">
            <NavigationBar />
            <Layout className="flex-grow items-stretch fcr-room-bg h-full">
              /** 调整 Layout 中 Content 与 Aside 的顺序。*/
              <Aside>
                <Room1v1StreamsContainer />
                <Chat />
              </Aside>
              <Layout
                className="flex-grow items-stretch relative"
                direction="col"
                style={{ paddingTop: 2 }}>
                <Whiteboard />
                <ScreenShareContainer />
                <WhiteboardToolbar />
                <ScenesController />
                <RemoteControlContainer />
                <StreamWindowsContainer />
                <RemoteControlToolbar />
              </Layout>
            </Layout>
            <DialogContainer />
            <LoadingContainer />
          </Layout>
          <WidgetContainer />
          <ToastContainer />
          <Watermark />
        </SceneSwitch>
      </FixedAspectRatioRootBox>
    </Room>
  );
};

Add a logo

If you want to add a logo to the right <Aside>, you need to implement the Logo component first, and then make the following changes in the packages/agora-classroom-sdk/src/infra/capabilities/scenarios/1v1/index.tsx file:

...
<Aside>
    <Logo/>
    <Room1v1StreamsContainer />
    <ChatWidgetPC />
</Aside>
...

Change the UI stores

A business component is composed of multiple functional components and depends on UI stores. This section introduces how to edit the UI stores.

UI stores are located in packages/agora-classroom-sdk/src/infra/stores:

FolderDescription
/commonThe common UI store for all scenarios
/interactiveThe UI store for the Small Classroom scenario
/lectureThe UI store for the Lecture Hall scenario
/lecture-h5The UI store for the Lecture Hall scenario in H5
/one-on-oneThe UI store for the One-to-one Classroom scenario

EduClassroomUIStore in /common is the base class. You can extend this class and rewrite the UI stores for a specific scenario.

The following example shows how to customize the UI store for the Lecture Hall scenario:

import { EduClassroomStore } from 'agora-edu-core';
import { EduClassroomUIStore } from '../common';
import { LectureBoardUIStore } from './board';
import { LectureRosterUIStore } from './roster';
import { LectureRoomStreamUIStore } from './stream';
import { LectrueToolbarUIStore } from './toolbar';

export class EduLectureUIStore extends EduClassroomUIStore {
  constructor(store: EduClassroomStore) {
    super(store);
    // Rewrite Stream UI Store
    this._streamUIStore = new LectureRoomStreamUIStore(store, this.shareUIStore);
    // Rewrite Roster UI Store
    this._rosterUIStore = new LectureRosterUIStore(store, this.shareUIStore);
    // Rewrite Board UI Store
    this._boardUIStore = new LectureBoardUIStore(store, this.shareUIStore);
    // Rewrite Toolbar UI Store
    this._toolbarUIStore = new LectrueToolbarUIStore(store, this.shareUIStore);
  }

  get streamUIStore() {
    return this._streamUIStore as LectureRoomStreamUIStore;
  }

  get rosterUIStore() {
    return this._rosterUIStore as LectureRosterUIStore;
  }
}

Change the teaching tools of the student after the teacher gives permission

If you want to change the teaching tools of the student after the teacher gives the student permission in all scenarios, edit toolbar/index.ts in /common. If you want to apply the above change to only one scenario, create a toolbar.ts file in the folder of this scenario and rewrite the relevant methods.

Taking One-to-one Classroom as an example, edit packages/agora-classroom-sdk/src/infra/stores/one-on-one/toolbar.ts, as follows:

// packages/agora-classroom-sdk/src/infra/stores/one-on-one/toolbar.ts
...
// Extend the base class ToolbarUIStore
export class OneToOneToolbarUIStore extends ToolbarUIStore {
  readonly allowedCabinetItems: string[] = [
    CabinetItemEnum.Whiteboard,
    CabinetItemEnum.ScreenShare,
    CabinetItemEnum.Laser,
  ];
  @computed
  get teacherTools(): ToolbarItem[] {
    let _tools: ToolbarItem[] = [];
    if (this.boardApi.mounted && !this.classroomStore.remoteControlStore.isHost) {
      _tools = [
        ToolbarItem.fromData({
          value: 'clicker',
          label: 'scaffold.clicker',
          icon: 'select',
          category: ToolbarItemCategory.Clicker,
        }),
        ToolbarItem.fromData({
          // Set the icon for the selector
          value: 'selection',
          label: 'scaffold.selector',
          icon: 'clicker',
          category: ToolbarItemCategory.Selector,
        }),
        ToolbarItem.fromData({
          value: 'pen',
          label: 'scaffold.pencil',
          icon: 'pen',
          category: ToolbarItemCategory.PenPicker,
        }),
        ToolbarItem.fromData({
          value: 'text',
          label: 'scaffold.text',
          icon: 'text',
          category: ToolbarItemCategory.Text,
        }),
        ToolbarItem.fromData({
          value: 'eraser',
          label: 'scaffold.eraser',
          icon: 'eraser',
          category: ToolbarItemCategory.Eraser,
        }),

        ToolbarItem.fromData({
          value: 'hand',
          label: 'scaffold.move',
          icon: 'hand',
          category: ToolbarItemCategory.Hand,
        }),
        ToolbarItem.fromData({
          value: 'save',
          label: 'scaffold.save',
          icon: 'save-ghost',
          category: ToolbarItemCategory.Save,
        }),
        {
          value: 'cloud',
          label: 'scaffold.cloud_storage',
          icon: 'cloud',
          category: ToolbarItemCategory.CloudStorage,
        },
        {
          value: 'tools',
          label: 'scaffold.tools',
          icon: 'tools',
          category: ToolbarItemCategory.Cabinet,
        },
      ];

      if (AgoraRteEngineConfig.platform === AgoraRteRuntimePlatform.Electron) {
        _tools.splice(
          5,
          0,
          ToolbarItem.fromData({
            value: 'slice',
            label: 'scaffold.slice',
            icon: 'slice',
            category: ToolbarItemCategory.Slice,
          }),
        );
      }
    } else {
      _tools = [
        {
          value: 'tools',
          label: 'scaffold.tools',
          icon: 'tools',
          category: ToolbarItemCategory.Cabinet,
        },
      ];
    }
    return _tools;
  }

  @computed
  get studentTools(): ToolbarItem[] {
    const { sessionInfo } = EduClassroomConfig.shared;
    const whiteboardAuthorized = this.boardApi.grantedUsers.has(sessionInfo.userUuid);

    if (!whiteboardAuthorized || this.classroomStore.remoteControlStore.isHost) {
      return [];
    }

    return [
      ToolbarItem.fromData({
        value: 'clicker',
        label: 'scaffold.clicker',
        icon: 'select',
        category: ToolbarItemCategory.Selector,
      }),
      ToolbarItem.fromData({
        // Set the icon for the selector
        value: 'selection',
        label: 'scaffold.selector',
        icon: 'clicker',
        category: ToolbarItemCategory.Clicker,
      }),
      ToolbarItem.fromData({
        value: 'pen',
        label: 'scaffold.pencil',
        icon: 'pen',
        category: ToolbarItemCategory.PenPicker,
      }),
      ToolbarItem.fromData({
        value: 'text',
        label: 'scaffold.text',
        icon: 'text',
        category: ToolbarItemCategory.Text,
      }),
      ToolbarItem.fromData({
        value: 'eraser',
        label: 'scaffold.eraser',
        icon: 'eraser',
        category: ToolbarItemCategory.Eraser,
      }),
    ];
  }
}

The above settings can override the teaching tools in /common. The final effect is as follows:

Additional examples

Change the background color of the classroom

To change the background color of the classroom, edit packages/agora-classroom-sdk/src/infra/capabilities/containers/root-box/fixed-aspect-ratio.tsx.

const FixedAspectRatioContainer: React.FC<FixedAspectRatioProps> = observer(
    ({children, minimumWidth = 0, minimumHeight = 0}) => {
        const style = useClassroomStyle({minimumHeight, minimumWidth});

        const {shareUIStore} = useStore();

        return (
            <div
                // You can use the tailwind class name
                className="flex bg-black justify-center items-center h-screen w-screen"
                // You can also set CSS properties
                style={{backgroundColor: "red"}}>
                <div style={style} className={`w-full h-full relative ${shareUIStore.classroomViewportClassName}`}>
                    {children}
                </div>
            </div>
        );
    },
);

Change the background color of the whiteboard

To change the background color of the whiteboard, edit packages/agora-plugin-gallery/src/gallery/whiteboard/style.css.

.netless-whiteboard-wrapper {
  height: 100%;
  width: 100%;
  border: 1px solid;
  border-radius: 4px;
  @apply bg-foreground border-divider;
  background: #000; /* Here we set the background color as black */
}

Adjust the whiteboard size

To adjust the whiteboard layout, edit packages/agora-classroom-sdk/src/infra/stores/common/board/index.ts. Flexible Classroom will first calculate the width and height of the overall classroom area according to packages/agora-classroom-sdk/src/infra/stores/common/share/index.ts, then calculate the height of the whiteboard container, and finally dynamically set the size of the whiteboard according to the ratio of the whiteboard to the whiteboard container.viewportAspectRatio heightRatio

// packages/agora-classroom-sdk/src/infra/stores/common/share/index.ts
...
// Set Classroom Size
updateClassroomViewportSize() {
  ...
    // Get the current window width and height
    const { width, height } = getRootDimensions(this._containerNode);

    const aspectRatio = this._viewportAspectRatio;

    const curAspectRatio = height / width;

    const scopeSize = { height, width };
    // Ensure classroom maintains a fixed aspect ratio
    if (curAspectRatio > aspectRatio) {
      // shrink height
      scopeSize.height = width * aspectRatio;
    } else if (curAspectRatio < aspectRatio) {
      // shrink width
      scopeSize.width = height / aspectRatio;
    }
  ...
}
...
// packages/agora-classroom-sdk/src/infra/stores/common/board/index.ts
// Set Whiteboard Scale
...
  protected get uiOverrides() {
    return {
      ...super.uiOverrides,
      heightRatio: 1
    };
  }

  /**
   * Whiteboard container height
   * @returns
   */
  @computed
  get boardAreaHeight() {
    // Set the height of the interactive area of ​​the whiteboard (Total minus the height of the navigation bar)
    const viewportHeight =
      this.shareUIStore.classroomViewportSize.height - this.shareUIStore.navHeight;
    // Set Whiteboard Scale
    const heightRatio = this.getters.stageVisible ? this.uiOverrides.heightRatio : 1;
    // Set whiteboard height
    const height = heightRatio * viewportHeight;

    return height;
  }
...

The above changes are applied to all scenarios. If you only want to change the height of the whiteboard in a the One-to-one Classroom scenario, create a board.ts file in packages/agora-classroom-sdk/src/infra/stores/one-on-one with the following code:

// packages/agora-classroom-sdk/src/infra/stores/one-on-one/board.ts
import {BoardUIStore} from "../common/board-ui";

export class OneToOneBoardUIStore extends BoardUIStore {
    protected get uiOverrides() {
        return {
            ...super.uiOverrides,
            heightRatio: 1,
        };
    }
}