├── src ├── generated │ └── .gitkeep ├── ui-kit │ ├── components │ │ ├── tree │ │ │ └── style.css │ │ ├── svga-player │ │ │ ├── svga-types.ts │ │ │ └── assets │ │ │ │ ├── star.gif │ │ │ │ ├── audio │ │ │ │ └── reward.mp3 │ │ │ │ └── svga │ │ │ │ ├── reward.svga │ │ │ │ └── hands-up.svga │ │ ├── button │ │ │ ├── abutton.css │ │ │ └── abutton.tsx │ │ ├── root-box │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── loading │ │ │ └── assets │ │ │ │ ├── loading.gif │ │ │ │ └── circle-loading.gif │ │ ├── roster │ │ │ ├── assets │ │ │ │ └── loading.gif │ │ │ └── hooks.tsx │ │ ├── placeholder │ │ │ ├── assets │ │ │ │ ├── no-body.png │ │ │ │ ├── no-file.png │ │ │ │ ├── camera-broken.png │ │ │ │ ├── camera-close.png │ │ │ │ ├── empty-history.png │ │ │ │ ├── camera-disabled.png │ │ │ │ └── board-disconnected.png │ │ │ ├── index.stories.tsx │ │ │ └── index.css │ │ ├── card │ │ │ ├── index.css │ │ │ ├── index.stories.tsx │ │ │ └── index.tsx │ │ ├── svg-img │ │ │ ├── paths │ │ │ │ ├── zoom-out.tsx │ │ │ │ ├── backward.tsx │ │ │ │ ├── forward.tsx │ │ │ │ ├── more.tsx │ │ │ │ ├── zoom-in.tsx │ │ │ │ ├── star.tsx │ │ │ │ ├── circle.tsx │ │ │ │ ├── line.tsx │ │ │ │ ├── chevron-right.tsx │ │ │ │ ├── down.tsx │ │ │ │ ├── dropdown.tsx │ │ │ │ ├── min.tsx │ │ │ │ ├── undo.tsx │ │ │ │ ├── redo.tsx │ │ │ │ ├── max.tsx │ │ │ │ ├── pretest-checked.tsx │ │ │ │ ├── eraser.tsx │ │ │ │ ├── mark.tsx │ │ │ │ ├── close.tsx │ │ │ │ ├── square.tsx │ │ │ │ ├── fullscreen.tsx │ │ │ │ ├── red-caution.tsx │ │ │ │ ├── fullscreen-shrink.tsx │ │ │ │ ├── select.tsx │ │ │ │ ├── bad-signal.tsx │ │ │ │ ├── pic.tsx │ │ │ │ ├── checked.tsx │ │ │ │ ├── copy.tsx │ │ │ │ ├── triangle-down.tsx │ │ │ │ ├── pretest-check.tsx │ │ │ │ ├── clock.tsx │ │ │ │ ├── camera.tsx │ │ │ │ ├── set.tsx │ │ │ │ ├── mic-enabled.tsx │ │ │ │ ├── none.tsx │ │ │ │ ├── tools.tsx │ │ │ │ ├── pen-line.tsx │ │ │ │ ├── room-label.tsx │ │ │ │ ├── mute-mobile.tsx │ │ │ │ ├── pen-circle.tsx │ │ │ │ ├── text.tsx │ │ │ │ ├── word.tsx │ │ │ │ ├── add-scene.tsx │ │ │ │ ├── pen-square.tsx │ │ │ │ ├── cloud-more.tsx │ │ │ │ ├── arrow.tsx │ │ │ │ ├── cloud.tsx │ │ │ │ ├── add.tsx │ │ │ │ ├── record.tsx │ │ │ │ ├── pen-curve.tsx │ │ │ │ ├── save-ghost.tsx │ │ │ │ ├── id.tsx │ │ │ │ ├── whiteboard.tsx │ │ │ │ ├── pen-triangle.tsx │ │ │ │ ├── camera-on-mobile.tsx │ │ │ │ ├── share-mobile.tsx │ │ │ │ ├── edit.tsx │ │ │ │ ├── pen-arrow.tsx │ │ │ │ ├── video.tsx │ │ │ │ ├── review.tsx │ │ │ │ ├── auto-play-failed.tsx │ │ │ │ ├── star-outline.tsx │ │ │ │ ├── microphone.tsx │ │ │ │ ├── register.tsx │ │ │ │ ├── reset.tsx │ │ │ │ ├── clear.tsx │ │ │ │ ├── normal-signal.tsx │ │ │ │ ├── triangle.tsx │ │ │ │ ├── emoji.tsx │ │ │ │ ├── vote.tsx │ │ │ │ ├── stage.tsx │ │ │ │ ├── pen-rhombus.tsx │ │ │ │ ├── stream-window-on.tsx │ │ │ │ ├── recording.tsx │ │ │ │ ├── triangle-solid-down.tsx │ │ │ │ ├── mic-disabled.tsx │ │ │ │ ├── video-gallery.tsx │ │ │ │ ├── on-podium.tsx │ │ │ │ ├── hand.tsx │ │ │ │ ├── pentagram.tsx │ │ │ │ ├── camera-off-mobile.tsx │ │ │ │ ├── pen.tsx │ │ │ │ ├── rhombus.tsx │ │ │ │ ├── camera-enabled.tsx │ │ │ │ ├── log.tsx │ │ │ │ ├── placeholder-no-setup.tsx │ │ │ │ ├── triangle-solid-up.tsx │ │ │ │ ├── settings.tsx │ │ │ │ ├── triangle-solid-right.tsx │ │ │ │ ├── excel.tsx │ │ │ │ ├── delete.tsx │ │ │ │ ├── goonstage.tsx │ │ │ │ ├── speaker.tsx │ │ │ │ ├── clicker.tsx │ │ │ │ ├── switch-screen-share.tsx │ │ │ │ └── hands-up.tsx │ │ │ ├── generate.ts │ │ │ ├── index.css │ │ │ ├── svg-dict.tsx │ │ │ └── index.stories.tsx │ │ ├── tabs │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── pagination │ │ │ ├── index.tsx │ │ │ └── index.css │ │ ├── progress │ │ │ ├── index.stories.tsx │ │ │ └── index.css │ │ ├── util │ │ │ ├── colors.ts │ │ │ ├── getRenderPropValue.ts │ │ │ ├── type.ts │ │ │ └── motion.ts │ │ ├── slider │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── input │ │ │ ├── ainput.tsx │ │ │ └── ainput.css │ │ ├── sound-player │ │ │ └── index.tsx │ │ ├── overlay-wrap │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── layout │ │ │ └── index.css │ │ ├── index.ts │ │ ├── select │ │ │ ├── assets │ │ │ │ └── arrow-icon.svg │ │ │ └── index.stories.tsx │ │ ├── popover │ │ │ └── index.stories.tsx │ │ ├── float │ │ │ └── index.tsx │ │ ├── checkbox │ │ │ └── index.stories.tsx │ │ ├── input-number │ │ │ └── index.css │ │ ├── modal │ │ │ └── index.stories.tsx │ │ ├── toast │ │ │ └── index.css │ │ ├── toolbar │ │ │ └── util.ts │ │ └── volume │ │ │ └── index.stories.tsx │ ├── index.ts │ ├── styles │ │ └── global.css │ └── utilities │ │ ├── state-color.ts │ │ ├── types.ts │ │ └── index.ts ├── infra │ ├── capabilities │ │ ├── containers │ │ │ ├── toast │ │ │ │ ├── index.css │ │ │ │ ├── index.mobile.css │ │ │ │ ├── index.tsx │ │ │ │ └── index.mobile.tsx │ │ │ ├── stream-window │ │ │ │ └── index.css │ │ │ ├── root-box │ │ │ │ └── index.tsx │ │ │ ├── roster │ │ │ │ ├── index.tsx │ │ │ │ └── user-list.tsx │ │ │ ├── stream │ │ │ │ └── assets │ │ │ │ │ ├── audio │ │ │ │ │ └── reward.mp3 │ │ │ │ │ ├── svga │ │ │ │ │ ├── reward.svga │ │ │ │ │ ├── wave-arm.gif │ │ │ │ │ ├── hands-up.svga │ │ │ │ │ ├── video-loadings.gif │ │ │ │ │ └── video-play.svg │ │ │ │ │ └── room-placholder-bg.jpg │ │ │ ├── pretest │ │ │ │ ├── assets │ │ │ │ │ ├── pretest-audio.mp3 │ │ │ │ │ └── modal-bg.svg │ │ │ │ └── volume.tsx │ │ │ ├── nav │ │ │ │ └── assets │ │ │ │ │ └── svga │ │ │ │ │ └── record-loading.svga │ │ │ ├── dialog │ │ │ │ ├── assets │ │ │ │ │ └── screen-share │ │ │ │ │ │ ├── student.png │ │ │ │ │ │ └── teacher.png │ │ │ │ ├── remote-control-confirm │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.tsx │ │ │ │ ├── error-generic.tsx │ │ │ │ └── video-gallery │ │ │ │ │ └── pager.css │ │ │ ├── fragments │ │ │ │ └── video-gallery │ │ │ │ │ └── context.ts │ │ │ ├── scene-switch │ │ │ │ └── index.css │ │ │ ├── hand-up │ │ │ │ ├── types.ts │ │ │ │ ├── invite-container.css │ │ │ │ └── invite-confirm.css │ │ │ ├── loading │ │ │ │ ├── index.css │ │ │ │ └── index.tsx │ │ │ ├── widget │ │ │ │ └── index.css │ │ │ ├── cloud-driver │ │ │ │ ├── cloud-more-menu.tsx │ │ │ │ └── index.tsx │ │ │ ├── scenes-controller │ │ │ │ └── index.css │ │ │ ├── screen-share │ │ │ │ ├── index.mobile.tsx │ │ │ │ └── index.css │ │ │ ├── device-setting │ │ │ │ └── index.tsx │ │ │ ├── toolbar │ │ │ │ ├── slice │ │ │ │ │ └── index.tsx │ │ │ │ └── board-cleaners │ │ │ │ │ └── index.tsx │ │ │ ├── aside │ │ │ │ └── index.tsx │ │ │ ├── award │ │ │ │ └── index.tsx │ │ │ └── camera-preview │ │ │ │ └── index.tsx │ │ ├── scenarios │ │ │ ├── big-class-mobile │ │ │ │ ├── after-class.png │ │ │ │ └── generic-error.png │ │ │ └── room │ │ │ │ └── index.tsx │ │ └── config.ts │ ├── api │ │ ├── polyfills.ts │ │ ├── lock.ts │ │ └── providers.tsx │ ├── stores │ │ ├── interactive │ │ │ ├── board.ts │ │ │ └── index.ts │ │ ├── common │ │ │ ├── hand-up │ │ │ │ └── type.ts │ │ │ ├── type.ts │ │ │ ├── pretest │ │ │ │ ├── helper.ts │ │ │ │ └── type.ts │ │ │ ├── layout │ │ │ │ └── helper.ts │ │ │ ├── subscription │ │ │ │ └── type.ts │ │ │ ├── stream-window │ │ │ │ └── type.ts │ │ │ ├── cloud-drive │ │ │ │ └── type.ts │ │ │ └── roster │ │ │ │ └── type.ts │ │ ├── one-on-one │ │ │ ├── index.ts │ │ │ └── stream.ts │ │ ├── lecture │ │ │ ├── board.ts │ │ │ └── index.ts │ │ └── lecture-mobile │ │ │ └── index.ts │ ├── utils │ │ ├── interaction.ts │ │ ├── extract.ts │ │ ├── error.ts │ │ ├── async-queue.ts │ │ └── event-center.ts │ ├── hooks │ │ ├── index.ts │ │ ├── cabinet.ts │ │ └── utilites.ts │ └── contexts │ │ └── ui-store-factory.ts └── react-app-env.d.ts ├── tailwind.config.js ├── .storybook ├── manager.js ├── customize-theme.js └── main.js ├── Jenkinsfile_bitbucket.groovy ├── typedoc.json ├── .gitignore ├── README.md ├── .npmignore ├── webpack.config.js ├── .github └── workflows │ └── gitee-sync.yml ├── postcss.config.js ├── ci └── build │ └── build_mac.sh └── tsconfig.json /src/generated/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ui-kit/components/tree/style.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ui-kit/components/svga-player/svga-types.ts: -------------------------------------------------------------------------------- 1 | export type SvgaTypes = 'reward'; 2 | -------------------------------------------------------------------------------- /src/ui-kit/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | export * from './utilities'; 3 | -------------------------------------------------------------------------------- /src/ui-kit/components/button/abutton.css: -------------------------------------------------------------------------------- 1 | .fcr-theme.ant-btn { 2 | border-radius: 8px; 3 | } 4 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/toast/index.css: -------------------------------------------------------------------------------- 1 | .toast-animation-exit { 2 | opacity: 0; 3 | } 4 | -------------------------------------------------------------------------------- /src/ui-kit/components/root-box/index.css: -------------------------------------------------------------------------------- 1 | .root-box { 2 | height: 100%; 3 | overflow: hidden; 4 | } 5 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/stream-window/index.css: -------------------------------------------------------------------------------- 1 | .stream-window { 2 | pointer-events: all; 3 | } 4 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require('agora-common-libs/presets/tailwind.config.js')], 3 | }; 4 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/root-box/index.tsx: -------------------------------------------------------------------------------- 1 | export { FixedAspectRatioRootBox, TrackArea } from './fixed-aspect-ratio'; 2 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/roster/index.tsx: -------------------------------------------------------------------------------- 1 | export { MidRosterBtn, BigRosterBtn } from './button'; 2 | 3 | export { RosterContainer } from './user-list'; 4 | -------------------------------------------------------------------------------- /src/ui-kit/components/loading/assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/loading/assets/loading.gif -------------------------------------------------------------------------------- /src/ui-kit/components/roster/assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/roster/assets/loading.gif -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import { addons } from '@storybook/addons'; 2 | import { theme } from './customize-theme'; 3 | 4 | addons.setConfig({ 5 | theme: theme, 6 | }); 7 | -------------------------------------------------------------------------------- /Jenkinsfile_bitbucket.groovy: -------------------------------------------------------------------------------- 1 | 2 | @Library('agora-build-pipeline-library') _ 3 | 4 | pipelineLoad(this, "ClassroomSDK", "workflow", "", "", "cloudclass-desktop") 5 | -------------------------------------------------------------------------------- /src/ui-kit/components/svga-player/assets/star.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/svga-player/assets/star.gif -------------------------------------------------------------------------------- /src/ui-kit/components/placeholder/assets/no-body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/placeholder/assets/no-body.png -------------------------------------------------------------------------------- /src/ui-kit/components/placeholder/assets/no-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/placeholder/assets/no-file.png -------------------------------------------------------------------------------- /src/infra/api/polyfills.ts: -------------------------------------------------------------------------------- 1 | //@ts-ignore 2 | require('matchmedia-polyfill'); 3 | require('matchmedia-polyfill/matchMedia.addListener'); 4 | require('promise-polyfill/src/polyfill'); -------------------------------------------------------------------------------- /src/ui-kit/components/loading/assets/circle-loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/loading/assets/circle-loading.gif -------------------------------------------------------------------------------- /src/ui-kit/components/placeholder/assets/camera-broken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/placeholder/assets/camera-broken.png -------------------------------------------------------------------------------- /src/ui-kit/components/placeholder/assets/camera-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/placeholder/assets/camera-close.png -------------------------------------------------------------------------------- /src/ui-kit/components/placeholder/assets/empty-history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/placeholder/assets/empty-history.png -------------------------------------------------------------------------------- /src/ui-kit/components/svga-player/assets/audio/reward.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/svga-player/assets/audio/reward.mp3 -------------------------------------------------------------------------------- /src/ui-kit/components/svga-player/assets/svga/reward.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/svga-player/assets/svga/reward.svga -------------------------------------------------------------------------------- /src/ui-kit/components/card/index.css: -------------------------------------------------------------------------------- 1 | .fcr-card { 2 | box-shadow: 0px 2px 6px 0px rgba(47, 65, 146, 0.15); 3 | @apply fcr-bg-component fcr-flex fcr-justify-center fcr-items-center; 4 | } 5 | -------------------------------------------------------------------------------- /src/ui-kit/components/placeholder/assets/camera-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/placeholder/assets/camera-disabled.png -------------------------------------------------------------------------------- /src/ui-kit/components/svga-player/assets/svga/hands-up.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/svga-player/assets/svga/hands-up.svga -------------------------------------------------------------------------------- /src/ui-kit/components/placeholder/assets/board-disconnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/ui-kit/components/placeholder/assets/board-disconnected.png -------------------------------------------------------------------------------- /src/infra/capabilities/containers/stream/assets/audio/reward.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/infra/capabilities/containers/stream/assets/audio/reward.mp3 -------------------------------------------------------------------------------- /src/infra/capabilities/containers/stream/assets/svga/reward.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/infra/capabilities/containers/stream/assets/svga/reward.svga -------------------------------------------------------------------------------- /src/infra/capabilities/containers/stream/assets/svga/wave-arm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/infra/capabilities/containers/stream/assets/svga/wave-arm.gif -------------------------------------------------------------------------------- /src/infra/capabilities/scenarios/big-class-mobile/after-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/infra/capabilities/scenarios/big-class-mobile/after-class.png -------------------------------------------------------------------------------- /src/infra/capabilities/containers/pretest/assets/pretest-audio.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/infra/capabilities/containers/pretest/assets/pretest-audio.mp3 -------------------------------------------------------------------------------- /src/infra/capabilities/containers/stream/assets/svga/hands-up.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/infra/capabilities/containers/stream/assets/svga/hands-up.svga -------------------------------------------------------------------------------- /src/infra/capabilities/scenarios/big-class-mobile/generic-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/infra/capabilities/scenarios/big-class-mobile/generic-error.png -------------------------------------------------------------------------------- /src/infra/capabilities/containers/nav/assets/svga/record-loading.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/infra/capabilities/containers/nav/assets/svga/record-loading.svga -------------------------------------------------------------------------------- /src/infra/capabilities/containers/stream/assets/room-placholder-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/infra/capabilities/containers/stream/assets/room-placholder-bg.jpg -------------------------------------------------------------------------------- /src/infra/capabilities/containers/dialog/assets/screen-share/student.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/infra/capabilities/containers/dialog/assets/screen-share/student.png -------------------------------------------------------------------------------- /src/infra/capabilities/containers/dialog/assets/screen-share/teacher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/infra/capabilities/containers/dialog/assets/screen-share/teacher.png -------------------------------------------------------------------------------- /src/infra/capabilities/containers/stream/assets/svga/video-loadings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO-Community/CloudClass-Desktop/HEAD/src/infra/capabilities/containers/stream/assets/svga/video-loadings.gif -------------------------------------------------------------------------------- /src/ui-kit/styles/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | 3 | @tailwind components; 4 | 5 | @tailwind utilities; 6 | 7 | @tailwind screens; 8 | 9 | html, 10 | html body { 11 | @apply fcr-font-scenario; 12 | } 13 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["./src/infra/api"], 3 | "out": "./docs", 4 | "exclude": ["**/node_modules/**"], 5 | "excludeExternals": true, 6 | "excludePrivate": true, 7 | "hideGenerator": true 8 | } 9 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/fragments/video-gallery/context.ts: -------------------------------------------------------------------------------- 1 | import AgoraRtcEngine from 'agora-electron-sdk/types/Api'; 2 | import { createContext } from 'react'; 3 | 4 | export const RtcEngineContext = createContext<{ 5 | rtcEngine?: AgoraRtcEngine; 6 | }>({}); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .env-dev 4 | .env.dev 5 | .env.prod 6 | .DS_Store 7 | release 8 | build 9 | dist 10 | *.txt 11 | es 12 | lib 13 | *.zip 14 | .eslintcache 15 | packages/agora-log-sdk 16 | package-lock.json 17 | yarn.lock 18 | *.log 19 | .vscode/ 20 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.svga'; 3 | declare module '*.svg'; 4 | declare module '*.json'; 5 | declare module '*.mp3'; 6 | declare module '*.mp4'; 7 | declare module '*.png'; 8 | declare module '*.gif'; 9 | declare module '*.jpg'; 10 | -------------------------------------------------------------------------------- /src/infra/stores/interactive/board.ts: -------------------------------------------------------------------------------- 1 | import { BoardUIStore } from '../common/board'; 2 | 3 | export class InteractiveBoardUIStore extends BoardUIStore { 4 | protected get uiOverrides() { 5 | return { 6 | ...super.uiOverrides, 7 | heightRatio: 0.84, 8 | }; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/infra/stores/common/hand-up/type.ts: -------------------------------------------------------------------------------- 1 | export enum OnPodiumStateEnum { 2 | /** 3 | * 正在台上 4 | */ 5 | onPodiuming = 1, 6 | /** 7 | * 正在挥手 8 | */ 9 | waveArming = 2, 10 | /** 11 | * 正在被邀请 12 | */ 13 | inviteding = 3, 14 | /** 15 | * 空闲中 16 | */ 17 | idleing = 4, 18 | } 19 | -------------------------------------------------------------------------------- /src/infra/stores/common/type.ts: -------------------------------------------------------------------------------- 1 | export enum OrientationEnum { 2 | portrait = 'portrait', 3 | landscape = 'landscape', 4 | } 5 | 6 | export type ConfirmDialogAction = 'ok' | 'cancel'; 7 | 8 | export enum LayoutMaskCode { 9 | None = 0, 10 | StageVisible = 1, 11 | VideoGalleryVisible = 2, 12 | } 13 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/zoom-out.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ; 8 | 9 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/infra/capabilities/containers/scene-switch/index.css: -------------------------------------------------------------------------------- 1 | .scene-switch-loading { 2 | @apply fcr-bg-background; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | flex: 1; 7 | height: 100%; 8 | position: fixed; 9 | width: 100%; 10 | z-index: 999999; 11 | left: 0; 12 | top: 0; 13 | } 14 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/generate.ts: -------------------------------------------------------------------------------- 1 | // import fs from 'fs'; 2 | // import path from 'path'; 3 | 4 | // fs.readdirSync(path.resolve(__dirname, './paths')).forEach((d) => { 5 | // const value = d.replace('.tsx', ''); 6 | // const key = value.toUpperCase().replace(/-/g, '_'); 7 | 8 | // console.log(`${key} = '${value}',`); 9 | // }); 10 | -------------------------------------------------------------------------------- /src/infra/stores/common/pretest/helper.ts: -------------------------------------------------------------------------------- 1 | const virtualSoundCardPatternList = [/BlackHole/i, /SoundFlower/i, /AgoraALD/i]; 2 | 3 | export const matchVirtualSoundCardPattern = (deviceName: string) => { 4 | return virtualSoundCardPatternList.reduce((prev, pattern) => { 5 | pattern.test(deviceName) && (prev = true); 6 | return prev; 7 | }, false); 8 | }; 9 | -------------------------------------------------------------------------------- /src/ui-kit/components/tabs/index.css: -------------------------------------------------------------------------------- 1 | /* fix bug: tabs 某些 css 找不到,所以手动补充下*/ 2 | .fcr-theme .ant-tabs-tabpane-hidden { 3 | display: none; 4 | } 5 | 6 | .whiteboard .tabs-top { 7 | width: 100%; 8 | } 9 | .whiteboard .tabs-top .tabs-nav-list { 10 | overflow-x: auto; 11 | } 12 | .whiteboard .tabs-top .tabs-nav-operations { 13 | display: none !important; 14 | } 15 | -------------------------------------------------------------------------------- /src/infra/stores/common/pretest/type.ts: -------------------------------------------------------------------------------- 1 | export enum DeviceStateChangedReason { 2 | cameraFailed = 'pretest.device_not_working', 3 | micFailed = 'pretest.device_not_working', 4 | newDeviceDetected = 'new_device_detected', 5 | cameraUnplugged = 'pretest.camera_move_out', 6 | micUnplugged = 'pretest.mic_move_out', 7 | playbackUnplugged = 'pretest.playback_move_out', 8 | } 9 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/backward.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | export const viewBox = '0 0 1024 1024' -------------------------------------------------------------------------------- /.storybook/customize-theme.js: -------------------------------------------------------------------------------- 1 | import { create } from '@storybook/theming'; 2 | 3 | export const theme = create({ 4 | base: 'light', 5 | brandTitle: 'CloudClass UI Preview', 6 | // brandUrl: 'https://github.com/agoraio-community/CloudClass-Desktop.git', 7 | fontBase: "'helvetica neue', 'arial', 'PingFangSC', 'microsoft yahei'", 8 | fontCode: 'microsoft yahei', 9 | }); 10 | -------------------------------------------------------------------------------- /src/infra/stores/common/layout/helper.ts: -------------------------------------------------------------------------------- 1 | export const getRootDimensions = (containerNode: Window | HTMLElement) => { 2 | const height = 3 | containerNode instanceof Window ? containerNode.innerHeight : containerNode.clientHeight; 4 | const width = 5 | containerNode instanceof Window ? containerNode.innerWidth : containerNode.clientWidth; 6 | return { width, height }; 7 | }; 8 | -------------------------------------------------------------------------------- /src/infra/api/lock.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | __agora__fcr__locked: boolean; 4 | } 5 | } 6 | export const lock = () => { 7 | window.__agora__fcr__locked = true; 8 | }; 9 | 10 | export const unlock = () => { 11 | window.__agora__fcr__locked = false; 12 | }; 13 | 14 | export const isLocked = () => { 15 | return !!window.__agora__fcr__locked; 16 | }; 17 | -------------------------------------------------------------------------------- /src/ui-kit/components/pagination/index.tsx: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | import { FC } from 'react'; 3 | import RcPagination, { PaginationProps } from 'rc-pagination'; 4 | import 'rc-pagination/assets/index.css'; 5 | import './index.css'; 6 | 7 | export const Pagination: FC = (props) => { 8 | return ; 9 | }; 10 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/forward.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | 10 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/more.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | 10 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/zoom-in.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | 10 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/ui-kit/components/progress/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Progress } from '.'; 3 | 4 | export default { 5 | title: 'Components/Progress', 6 | }; 7 | 8 | export const ProgressShowCase = (props: any) => { 9 | return ; 10 | }; 11 | 12 | ProgressShowCase.args = { 13 | width: 100, 14 | progress: 100, 15 | }; 16 | -------------------------------------------------------------------------------- /src/ui-kit/components/button/abutton.tsx: -------------------------------------------------------------------------------- 1 | import Button, { ButtonProps } from 'antd/lib/button'; 2 | import { FC, PropsWithChildren } from 'react'; 3 | import './abutton.css'; 4 | type AButtonProps = Pick; 5 | 6 | export const AButton: FC> = ({ className = '', ...props }) => { 7 | return , 21 | ]} 22 | title={title}> 23 | {content} 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/pretest-check.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 14 | 21 | 25 | 26 | 27 | 28 | export const viewBox = '0 0 30 30'; 29 | 30 | -------------------------------------------------------------------------------- /src/infra/stores/one-on-one/stream.ts: -------------------------------------------------------------------------------- 1 | import { StreamUIStore } from '../common/stream'; 2 | 3 | export class OneToOneStreamUIStore extends StreamUIStore { 4 | private _teacherWidthRatio = 0.217; 5 | 6 | get toolbarPlacement(): 'bottom' | 'left' { 7 | return 'left'; 8 | } 9 | 10 | get toolbarOffset(): number[] { 11 | return [10, 0]; 12 | } 13 | 14 | get fullScreenToolbarOffset(): number[] { 15 | return [0, -58]; 16 | } 17 | 18 | get videoStreamSize() { 19 | const width = this.shareUIStore.classroomViewportSize.width * this._teacherWidthRatio; 20 | 21 | const height = (9 / 16) * width; 22 | 23 | return { width, height }; 24 | } 25 | 26 | onInstall(): void { 27 | super.onInstall(); 28 | 29 | this.classroomStore.mediaStore.setMirror(true); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpackMerge = require('webpack-merge'); 2 | const path = require('path'); 3 | const baseConfig = require('agora-common-libs/presets/webpack.config.base.js'); 4 | const ROOT_PATH = path.resolve(__dirname, './'); 5 | const config = { 6 | entry: { 7 | edu_sdk: './src/infra/api/index.tsx', 8 | }, 9 | output: { 10 | path: path.resolve(ROOT_PATH, 'lib'), 11 | publicPath: './', 12 | filename: '[name].bundle.js', 13 | libraryTarget: 'umd', 14 | clean: true, 15 | }, 16 | resolve: { 17 | alias: { 18 | '@classroom': path.resolve(ROOT_PATH, './src'), 19 | 'agora-classroom-sdk': path.resolve(ROOT_PATH, './src/infra/api'), 20 | }, 21 | }, 22 | }; 23 | 24 | const mergedConfig = webpackMerge.merge(baseConfig, config); 25 | module.exports = mergedConfig; 26 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/scenes-controller/index.css: -------------------------------------------------------------------------------- 1 | .scenes-controller-container { 2 | position: absolute; 3 | min-width: 170px; 4 | height: 34px !important; 5 | left: 10px; 6 | bottom: 18px; 7 | z-index: 2; 8 | width: auto !important; 9 | border-radius: 20px; 10 | @apply fcr-bg-component; 11 | } 12 | 13 | .scenes-controller-line { 14 | width: 1px; 15 | height: 16px; 16 | border-right: 1px solid #e5e5f0; 17 | margin-left: 6px; 18 | margin-right: 9px; 19 | } 20 | 21 | .scenes-controller-btn-list { 22 | display: flex; 23 | width: 100%; 24 | height: 100%; 25 | box-sizing: border-box; 26 | align-items: center; 27 | padding: 0 10px 0 16px; 28 | } 29 | 30 | .scenes-controller-info { 31 | @apply fcr-text-level2; 32 | font-size: 14px; 33 | user-select: none; 34 | margin: 0 6px; 35 | } 36 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/screen-share/index.mobile.tsx: -------------------------------------------------------------------------------- 1 | import { useLectureH5UIStores } from '@classroom/infra/hooks/ui-store'; 2 | import classnames from 'classnames'; 3 | import { observer } from 'mobx-react'; 4 | import './index.css'; 5 | import { ScreenShareRemoteTrackPlayer } from '.'; 6 | 7 | export const ScreenShareContainerMobile = observer(() => { 8 | const { 9 | boardUIStore: { boardContainerHeight }, 10 | streamUIStore: { screenShareStream }, 11 | } = useLectureH5UIStores(); 12 | 13 | const remotecls = classnames('remote-screen-share-container', 'fcr-absolute', 'fcr-top-0'); 14 | 15 | return screenShareStream ? ( 16 |
17 | 18 |
19 | ) : null; 20 | }); 21 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/clock.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | <> 7 | 16 | 22 | 23 | ); 24 | 25 | export const viewBox = '0 0 16 16'; 26 | -------------------------------------------------------------------------------- /src/infra/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { DependencyList, useEffect, useMemo, useRef } from 'react'; 2 | import { clickAnywhere } from '../utils'; 3 | import { interactionThrottleHandler } from '../utils/interaction'; 4 | 5 | export const useInteractionThrottleHandler = ( 6 | func: T, 7 | options: { limitMs?: number; limitFunc?: () => void }, 8 | deps: DependencyList, 9 | ) => { 10 | return useMemo(() => { 11 | return interactionThrottleHandler(func, options.limitFunc || (() => {}), { 12 | limitMs: options.limitMs, 13 | }) as T; 14 | }, deps); 15 | }; 16 | 17 | export const useClickAnywhere = (cb: () => void) => { 18 | const ref = useRef(null); 19 | 20 | useEffect(() => { 21 | if (ref.current) { 22 | return clickAnywhere(ref.current, cb); 23 | } 24 | }, []); 25 | 26 | return ref; 27 | }; 28 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/camera.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | 13 | ); 14 | 15 | export const viewBox = '0 0 28 28'; 16 | -------------------------------------------------------------------------------- /src/ui-kit/components/layout/index.css: -------------------------------------------------------------------------------- 1 | .fcr-layout { 2 | @apply fcr-flex; 3 | } 4 | 5 | .fcr-layout-row { 6 | @apply fcr-flex-row; 7 | } 8 | 9 | .fcr-layout-col { 10 | @apply fcr-flex-col; 11 | } 12 | .fcr-layout-col-reverse { 13 | @apply fcr-flex-col-reverse; 14 | } 15 | 16 | .fcr-layout-content { 17 | @apply fcr-flex fcr-flex-row fcr-flex-1 fcr-h-full; 18 | position: relative; 19 | width: 0; 20 | } 21 | 22 | .fcr-layout-header { 23 | @apply fcr-flex fcr-flex-row fcr-justify-center fcr-items-center fcr-w-full; 24 | flex: 0 0 27px; 25 | } 26 | 27 | .fcr-layout-aside { 28 | @apply fcr-flex fcr-flex-col fcr-border-l fcr-border-divider fcr-h-full fcr-overflow-hidden; 29 | /* width: 19rem; */ 30 | border-left: none; 31 | gap: 4px; 32 | margin-left: 2px; 33 | position: relative; 34 | z-index: 3; 35 | flex-shrink: 0; 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/gitee-sync.yml: -------------------------------------------------------------------------------- 1 | name: gitee-sync 2 | on: 3 | pull_request: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | name: gitee-sync 9 | runs-on: ubuntu-latest 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | if: github.actor != 'dependabot[bot]' 15 | steps: 16 | - name: Gitee sync repo 17 | uses: Yikun/hub-mirror-action@v1.3 18 | with: 19 | src: github/AgoraIO-Community 20 | dst: gitee/agoraio-community 21 | white_list: "CloudClass-Desktop" 22 | static_list: "CloudClass-Desktop" 23 | cache_path: "./cache" 24 | dst_key: ${{ secrets.GITEE_PI_SSH }} 25 | dst_token: ${{ secrets.GITEE_PRIVATE_TOKEN }} 26 | force_update: true 27 | account_type: org -------------------------------------------------------------------------------- /src/infra/capabilities/containers/loading/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react'; 2 | import { Card, Loading } from '@classroom/ui-kit'; 3 | import { useStore } from '@classroom/infra/hooks/ui-store'; 4 | import './index.css'; 5 | 6 | export const LoadingContainer = observer(() => { 7 | const { layoutUIStore } = useStore(); 8 | const { loading, loadingText } = layoutUIStore; 9 | return loading ? : null; 10 | }); 11 | 12 | const PageLoading = (props: { loadingText?: string }) => { 13 | const { loadingText } = props; 14 | return ( 15 |
16 | 17 | 18 | {loadingText &&

{loadingText}

} 19 |
20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/infra/utils/extract.ts: -------------------------------------------------------------------------------- 1 | import { EduStream, EduUserStruct } from 'agora-edu-core'; 2 | import { AgoraRteVideoSourceType } from 'agora-rte-sdk'; 3 | 4 | /** 5 | * 提取流列表 6 | */ 7 | export const extractUserStreams = ( 8 | users: Map, 9 | streamByUserUuid: Map>, 10 | streamByStreamUuid: Map, 11 | sourceTypes: AgoraRteVideoSourceType[], 12 | ) => { 13 | const streams = new Set(); 14 | for (const user of users.values()) { 15 | const streamUuids = streamByUserUuid.get(user.userUuid) || new Set(); 16 | for (const streamUuid of streamUuids) { 17 | const stream = streamByStreamUuid.get(streamUuid); 18 | 19 | if (stream && sourceTypes.includes(stream.videoSourceType)) { 20 | streams.add(stream); 21 | } 22 | } 23 | } 24 | return streams; 25 | }; 26 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/set.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /src/infra/utils/error.ts: -------------------------------------------------------------------------------- 1 | import { AGError } from 'agora-rte-sdk'; 2 | import { transI18n } from 'agora-common-libs'; 3 | 4 | /** 5 | * 返回错误提示信息 6 | * @param error 7 | * @returns 8 | */ 9 | export const getEduErrorMessage = (error: Error) => { 10 | if (error instanceof AGError && error.codeList && error.codeList.length) { 11 | const code = error.codeList[error.codeList.length - 1]; 12 | 13 | if (error.servCode && error.servCode !== -1) { 14 | return transI18n(`edu_serv_error.${error.servCode}`); 15 | } else { 16 | return transI18n(`edu_error.${code}`); 17 | } 18 | } 19 | 20 | return null; 21 | }; 22 | 23 | /** 24 | * 如果Error中包含有服务端错误码,则返回服务端错误码 25 | * @param error 26 | * @returns 27 | */ 28 | export const getErrorServCode = (error: Error) => { 29 | if (error instanceof AGError) { 30 | return error.servCode; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/ui-kit/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './button'; 2 | export * from './card'; 3 | export * from './checkbox'; 4 | export * from './float'; 5 | export * from './input'; 6 | export * from './input-number'; 7 | export * from './layout'; 8 | export * from './loading'; 9 | export * from './modal'; 10 | export * from './overlay-wrap'; 11 | export * from './pagination'; 12 | export * from './placeholder'; 13 | export * from './popover'; 14 | export * from './progress'; 15 | export * from './radio'; 16 | export * from './root-box'; 17 | export * from './roster'; 18 | export * from './sound-player'; 19 | export * from './svg-img'; 20 | export * from './svga-player'; 21 | export * from './table'; 22 | export * from './tabs'; 23 | export * from './toast'; 24 | export * from './toolbar'; 25 | export * from './tooltip'; 26 | export * from './tree'; 27 | export * from './volume'; 28 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/dialog/remote-control-confirm/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@classroom/ui-kit'; 2 | import { useI18n } from 'agora-common-libs'; 3 | import './index.css'; 4 | interface IPropsTypes { 5 | onOK: () => void; 6 | } 7 | export const RemoteControlConfirm = (props: IPropsTypes) => { 8 | const t = useI18n(); 9 | return ( 10 |
11 |
12 |
13 | 16 |
17 |
18 | {t('fcr_share_teacher_requesting_share')} 19 |
20 |
21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/device-setting/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react'; 2 | import { useStore } from '@classroom/infra/hooks/ui-store'; 3 | import './index.css'; 4 | import { RoomPretest } from '../pretest'; 5 | import { useEffect } from 'react'; 6 | 7 | export const RoomDeviceSettingContainer = observer(({ id }: { id: string }) => { 8 | const { 9 | shareUIStore: { removeDialog }, 10 | streamUIStore: { setSettingsOpened }, 11 | deviceSettingUIStore: { deviceStage }, 12 | } = useStore(); 13 | 14 | useEffect(() => { 15 | setSettingsOpened(true); 16 | return () => { 17 | setSettingsOpened(false); 18 | }; 19 | }, []); 20 | 21 | return ( 22 | removeDialog(id)} 26 | onCancel={() => removeDialog(id)} 27 | /> 28 | ); 29 | }); 30 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/mic-enabled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { PathOptions } from "../svg-dict" 3 | 4 | export const path = (props: PathOptions) => 5 | 6 | 7 | 8 | 9 | 10 | export const viewBox = "0 0 24 24" -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/none.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | 12 | ); 13 | export const viewBox = '0 0 40 40'; 14 | -------------------------------------------------------------------------------- /src/ui-kit/components/card/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta } from '@storybook/react'; 3 | import { Card } from '../card'; 4 | 5 | const meta: Meta = { 6 | title: 'Components/Card', 7 | component: Card, 8 | }; 9 | 10 | type DocsProps = { 11 | width: number; 12 | height: number; 13 | borderRadius: number; 14 | }; 15 | 16 | export const Docs = ({ width, height, borderRadius }: DocsProps) => ( 17 | <> 18 |
19 | 20 |

Hello Card !

21 |
22 |
23 |
24 | 25 |

自定义圆角

26 |
27 |
28 | 29 | ); 30 | 31 | Docs.args = { 32 | width: 250, 33 | height: 200, 34 | borderRadius: 12, 35 | }; 36 | 37 | export default meta; 38 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/tools.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/pen-line.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = ({ iconPrimary, penColor }: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | export const viewBox = '0 0 22 24'; 14 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/room-label.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | 7 | 13 | 14 | ); 15 | 16 | export const viewBox = '0 0 15 14'; 17 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/mute-mobile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = ({ iconPrimary }: PathOptions) => ( 6 | <> 7 | 14 | 15 | ); 16 | 17 | export const viewBox = '0 0 40 40'; 18 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/pen-circle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = ({ iconPrimary, penColor }: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | export const viewBox = '0 0 22 24'; 14 | -------------------------------------------------------------------------------- /src/ui-kit/components/select/assets/arrow-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 编组 39备份 3 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/text.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/word.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | 10 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/ui-kit/components/popover/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { Meta } from '@storybook/react'; 3 | import { Button } from '../button'; 4 | import { Popover, PopoverProps } from '../popover'; 5 | 6 | const meta: Meta = { 7 | title: 'Components/Popover', 8 | component: Popover, 9 | }; 10 | 11 | export const Docs: FC = (props) => { 12 | return ( 13 | <> 14 |
15 | 16 | 17 | 18 |
19 |
20 | Hello world!} 25 | > 26 | 27 | 28 |
29 | 30 | ); 31 | }; 32 | 33 | export default meta; 34 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/add-scene.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = ({ iconPrimary }: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | 12 | export const viewBox = '0 0 24 24' -------------------------------------------------------------------------------- /src/infra/capabilities/containers/screen-share/index.css: -------------------------------------------------------------------------------- 1 | .remote-screen-share-container { 2 | position: absolute; 3 | width: 100%; 4 | } 5 | 6 | .local-screen-share-container { 7 | display: flex; 8 | justify-content: center; 9 | position: absolute; 10 | width: 100%; 11 | top: 5px; 12 | z-index: 2; 13 | pointer-events: none; 14 | } 15 | 16 | .local-screen-share-container .stop-button { 17 | font-size: 12px; 18 | width: 100%; 19 | height: 100%; 20 | display: flex; 21 | border-radius: 5px; 22 | color: #357bf6; 23 | background: white; 24 | box-shadow: 0px 2px 8px 0px rgb(47 65 146 / 15%); 25 | align-items: center; 26 | justify-content: center; 27 | outline: none; 28 | pointer-events: all; 29 | } 30 | 31 | .local-screen-share-container .stop-button:hover { 32 | color: white; 33 | background: #357bf6; 34 | } 35 | 36 | .local-screen-share-container .stop-button > span { 37 | display: flex; 38 | margin-left: 2px; 39 | } 40 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/toolbar/slice/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react'; 2 | import { useStore } from '@classroom/infra/hooks/ui-store'; 3 | import { SvgImg, Slice, SvgIconEnum } from '@classroom/ui-kit'; 4 | import { useI18n } from 'agora-common-libs'; 5 | 6 | export const SliceContainer = observer(() => { 7 | const { toolbarUIStore } = useStore(); 8 | const t = useI18n(); 9 | const { sliceItems, handleSliceItem } = toolbarUIStore; 10 | 11 | const mappedItems = sliceItems.map((item) => { 12 | const { id, iconType, name } = item; 13 | return { 14 | id, 15 | icon: iconType ? : , 16 | name, 17 | }; 18 | }); 19 | 20 | return ( 21 | 28 | ); 29 | }); 30 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/pen-square.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = ({ iconPrimary, penColor }: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | export const viewBox = '0 0 22 24'; 14 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/cloud-more.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PathOptions } from '../svg-dict'; 3 | 4 | export const path = (props: PathOptions) => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | 20 | export const viewBox = '0 0 32 32'; 21 | -------------------------------------------------------------------------------- /src/infra/stores/lecture/board.ts: -------------------------------------------------------------------------------- 1 | import { EduClassroomConfig } from 'agora-edu-core'; 2 | import { reaction } from 'mobx'; 3 | import { BoardUIStore } from '../common/board'; 4 | 5 | export class LectureBoardUIStore extends BoardUIStore { 6 | protected get uiOverrides() { 7 | return { 8 | ...super.uiOverrides, 9 | heightRatio: 1, 10 | }; 11 | } 12 | onInstall(): void { 13 | super.onInstall(); 14 | this._disposers.push( 15 | reaction( 16 | () => this.classroomStore.roomStore.acceptedList, 17 | (acceptedList) => { 18 | const { userUuid } = EduClassroomConfig.shared.sessionInfo; 19 | const isOnPodium = acceptedList.some((item) => item.userUuid === userUuid); 20 | const isGranted = this.boardApi.grantedUsers.has(userUuid); 21 | 22 | if (!isOnPodium && isGranted) { 23 | this.boardApi.grantPrivilege(userUuid, false); 24 | } 25 | }, 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/arrow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | export const viewBox = '0 0 26 26' -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/cloud.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | export const viewBox = '0 0 26 26'; -------------------------------------------------------------------------------- /src/ui-kit/components/overlay-wrap/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from 'react'; 2 | import classnames from 'classnames'; 3 | import { BaseProps } from '../util/type'; 4 | import { CSSTransition } from 'react-transition-group'; 5 | import './index.css'; 6 | 7 | interface OverlayWrapProps extends BaseProps { 8 | opened?: boolean; 9 | centered?: boolean; 10 | onExited?: (() => void) | undefined; 11 | } 12 | 13 | /** 14 | * 15 | * @param param0 16 | * @returns 17 | */ 18 | export const OverlayWrap: FC> = ({ 19 | opened = true, 20 | onExited, 21 | className, 22 | children, 23 | centered = true, 24 | }) => { 25 | const cls = classnames('overlay-wrap', { 26 | 'overlay-wrap-content-ltr': !centered, 27 | [`${className}`]: !!className, 28 | }); 29 | return ( 30 | 31 |
{children}
32 |
33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/add.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = ({ iconPrimary }: PathOptions) => ( 6 | 12 | ); 13 | 14 | export const viewBox = '0 0 40 40'; 15 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/svg-dict.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ctxRequire = require.context('./paths', false, /\.tsx$/); 4 | 5 | const paths: Record = {}; 6 | 7 | ctxRequire.keys().forEach(async (path) => { 8 | const m = ctxRequire(path) as SvgPath; 9 | const key = path.replace('./', '').replace('.tsx', ''); 10 | paths[key] = m; 11 | }); 12 | 13 | export type PathOptions = { 14 | iconPrimary: string, 15 | iconSecondary: string, 16 | [key: string]: string 17 | }; 18 | 19 | export type SvgPath = { 20 | path: (options: PathOptions) => React.ReactNode; 21 | viewBox?: string; 22 | }; 23 | 24 | export const getPath = (name: string, props: PathOptions) => { 25 | const svg = paths[name]; 26 | 27 | if (svg) { 28 | return svg.path(props); 29 | } 30 | 31 | return 32 | 33 | }; 34 | 35 | export const getViewBox = (name: string) => { 36 | const svg = paths[name]; 37 | 38 | if (svg) { 39 | return svg.viewBox; 40 | } 41 | 42 | return '0 0 0 0'; 43 | }; 44 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/record.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | 10 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/ui-kit/components/float/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { FC, PropsWithChildren } from 'react'; 3 | import { BaseProps } from '../util/type'; 4 | 5 | export interface FloatProps extends BaseProps { 6 | top?: number; 7 | left?: number; 8 | bottom?: number; 9 | right?: number; 10 | direction?: 'row' | 'col'; 11 | align?: 'flex-start' | 'flex-end'; 12 | justify?: 'start' | 'end'; 13 | gap: number; 14 | } 15 | 16 | export const Float: FC> = ({ 17 | top, 18 | left, 19 | bottom, 20 | right, 21 | children, 22 | direction = 'row', 23 | align, 24 | justify, 25 | gap, 26 | }) => { 27 | const cls = classNames( 28 | 'fcr-absolute fcr-z-50 fcr-flex', 29 | direction === 'row' ? 'fcr-flex-row' : 'fcr-flex-col', 30 | `fcr-gap-${gap ?? 0}`, 31 | ); 32 | return ( 33 |
36 | {children} 37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/pen-curve.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = ({ iconPrimary, penColor }: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | export const viewBox = '0 0 22 24'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/save-ghost.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | typescript: { 5 | reactDocgen: 'react-docgen', 6 | }, 7 | stories: ['../src/ui-kit/components/**/*.stories.@(ts|tsx)'], 8 | addons: [ 9 | '@storybook/addon-links', 10 | '@storybook/addon-essentials', 11 | { 12 | name: '@storybook/addon-postcss', 13 | options: { 14 | postcssLoaderOptions: { 15 | // When using postCSS 8 16 | implementation: require('postcss'), 17 | }, 18 | }, 19 | }, 20 | ], 21 | webpackFinal: async (config) => { 22 | config.resolve.extensions.push('.ts', '.tsx'); 23 | config.resolve.alias = { 24 | ...config.resolve.alias, 25 | 'agora-common-libs': path.resolve(__dirname, '../../../node_modules/agora-common-libs/lib'), 26 | }; 27 | 28 | config.module.rules.push({ 29 | test: /agora-common-libs\/.*\/(annotation)|(widget)|(agora-rte-sdk)/, 30 | use: { loader: 'null-loader' }, 31 | }); 32 | 33 | return config; 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const autoprefixer = require('autoprefixer'); 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const tailwindcss = require('tailwindcss'); 5 | // eslint-disable-next-line @typescript-eslint/no-var-requires 6 | const tailwindConfig = require('./tailwind.config'); 7 | const postcssPxToViewport = require('agora-common-libs/presets/postcss-plugin/px-to-vw/index.js'); 8 | 9 | module.exports = { 10 | plugins: [ 11 | autoprefixer(), 12 | tailwindcss(tailwindConfig), 13 | postcssPxToViewport({ 14 | viewportWidth: 375, 15 | unitPrecision: 5, 16 | viewportUnit: 'vw', 17 | fontViewportUnit: 'vw', 18 | include: [/\/mobile\//, /\.mobile\./], 19 | exclude: [/\/node_modules\//i], 20 | landscape: true, // 是否处理横屏情况 21 | landscapeUnit: 'vw', // (String) 横屏时使用的单位 22 | landscapeWidth: 812, // (Number) 横屏时使用的视口宽度 23 | landscapeHeight: 375, // (Number) 横屏时使用的视口宽度 24 | }), 25 | ], 26 | }; 27 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/aside/index.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '@classroom/infra/hooks/ui-store'; 2 | import { LectureRoomStreamUIStore } from '@classroom/infra/stores/lecture/stream'; 3 | import { OneToOneStreamUIStore } from '@classroom/infra/stores/one-on-one/stream'; 4 | import { observer } from 'mobx-react'; 5 | import { FC, PropsWithChildren } from 'react'; 6 | import { Aside } from '@classroom/ui-kit'; 7 | 8 | export const BigClassAside: FC = observer(({ children }) => { 9 | const { streamUIStore } = useStore(); 10 | return ( 11 | 15 | ); 16 | }); 17 | 18 | export const OneToOneClassAside: FC = observer(({ children }) => { 19 | const { streamUIStore } = useStore(); 20 | return ( 21 | 24 | ); 25 | }); 26 | -------------------------------------------------------------------------------- /ci/build/build_mac.sh: -------------------------------------------------------------------------------- 1 | source_root=$(pwd) 2 | ci_source_root=../apaas-cicd-web 3 | build_branch=$cloudclass_desktop_branch 4 | 5 | ci_script_version=v1 6 | 7 | # . ../apaas-cicd-web/versions.sh 8 | . ../apaas-cicd-web/utilities/tools.sh 9 | . ../apaas-cicd-web/build/$ci_script_version/dependency.sh 10 | . ../apaas-cicd-web/build/$ci_script_version/build.sh 11 | 12 | # pick up agora-rte-sdk agora-edu-core agora-common-libs 13 | lib_dependencies=(${lib_dependencies[@]:0:3}) 14 | lib_versions=(${lib_versions[@]:0:3}) 15 | lib_branches=(${lib_branches[@]:0:3}) 16 | 17 | if [ "$debug" == "true" ]; then 18 | # show environment variables 19 | echo "------------- variables --------------------" 20 | set 21 | echo "--------------------------------------------" 22 | fi 23 | 24 | download_packages $source_root $build_branch "${lib_dependencies[*]}" "${lib_versions[*]}" "${lib_branches[*]}" 25 | 26 | make_monorepo $source_root 27 | 28 | install_packages $source_root 29 | 30 | build_lib $source_root $ci_source_root agora-classroom-sdk $build_branch 31 | -------------------------------------------------------------------------------- /src/ui-kit/utilities/index.ts: -------------------------------------------------------------------------------- 1 | import { Z_INDEX_RULES } from './style-config'; 2 | 3 | export type I18nLanguage = 'zh' | 'en'; 4 | 5 | export const getOS = () => { 6 | let ua = navigator.userAgent, 7 | isWindowsPhone = /(?:Windows Phone)/.test(ua), 8 | isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone, 9 | isAndroid = /(?:Android)/.test(ua), 10 | isFireFox = /(?:Firefox)/.test(ua), 11 | isChrome = /(?:Chrome|CriOS)/.test(ua), 12 | isTablet = 13 | /(?:iPad|PlayBook)/.test(ua) || 14 | (isAndroid && !/(?:Mobile)/.test(ua)) || 15 | (isFireFox && /(?:Tablet)/.test(ua)) || 16 | (navigator.maxTouchPoints && 17 | navigator.maxTouchPoints > 2 && 18 | /MacIntel/.test(navigator.platform)) || 19 | 'ontouchend' in document, 20 | isPhone = /(?:iPhone)/.test(ua) && !isTablet, 21 | isPc = !isPhone && !isAndroid && !isSymbian; 22 | return { 23 | isTablet: isTablet, 24 | isPhone: isPhone, 25 | isPc: isPc, 26 | }; 27 | }; 28 | 29 | export const Z_INDEX_CONST = Z_INDEX_RULES; 30 | -------------------------------------------------------------------------------- /src/infra/hooks/cabinet.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { useStore } from './ui-store'; 3 | 4 | export const useExtensionCabinets = () => { 5 | const { widgetUIStore, classroomStore } = useStore(); 6 | 7 | const openExtensionCabinet = useCallback((id: string, remote = true) => { 8 | const nextZIndex = 9 | classroomStore.widgetStore.widgetController?.zIndexController.incrementZIndex(); 10 | 11 | const trackProps = { position: { xaxis: 0.5, yaxis: 0.5 }, zIndex: nextZIndex }; 12 | widgetUIStore.createWidget(id, { 13 | trackProperties: trackProps, 14 | userProperties: {}, 15 | properties: {}, 16 | }); 17 | 18 | if (remote) { 19 | classroomStore.widgetStore.widgetController?.setWidegtActive(id, trackProps); 20 | } 21 | }, []); 22 | 23 | const isInstalled = useCallback((id: string) => { 24 | const names = widgetUIStore.registeredWidgetNames; 25 | return names.includes(id); 26 | }, []); 27 | 28 | return { 29 | openExtensionCabinet, 30 | isInstalled, 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /src/ui-kit/components/card/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, EventHandler } from 'react'; 2 | import classnames from 'classnames'; 3 | import { BaseProps } from '../../components/util/type'; 4 | import './index.css'; 5 | 6 | export interface CardProps extends BaseProps { 7 | width?: number; 8 | height?: number; 9 | borderRadius?: number | string; 10 | children?: React.ReactNode; 11 | onMouseDown?: EventHandler; 12 | onMouseUp?: EventHandler; 13 | onMouseLeave?: EventHandler; 14 | onScroll?: EventHandler; 15 | } 16 | 17 | export const Card: FC = ({ 18 | width = 90, 19 | height = 90, 20 | borderRadius = 12, 21 | children, 22 | className, 23 | ...restProps 24 | }) => { 25 | const cls = classnames({ 26 | [`fcr-card`]: 1, 27 | [`${className}`]: !!className, 28 | }); 29 | return ( 30 |
38 | {children} 39 |
40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/id.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | 7 | 13 | 14 | ); 15 | 16 | export const viewBox = '0 0 48 48'; 17 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/whiteboard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/pen-triangle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = ({ iconPrimary, penColor }: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | export const viewBox = '0 0 22 24'; 14 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/camera-on-mobile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = ({ iconPrimary }: PathOptions) => ( 6 | 13 | ); 14 | 15 | export const viewBox = '0 0 40 40'; 16 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/award/index.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '@classroom/infra/hooks/ui-store'; 2 | import { observer } from 'mobx-react'; 3 | import { SvgaPlayer, SoundPlayer } from '@classroom/ui-kit'; 4 | 5 | import RewardSVGA from '../stream/assets/svga/reward.svga'; 6 | import RewardSound from '../stream/assets/audio/reward.mp3'; 7 | 8 | export const Award = observer(() => { 9 | const { 10 | layoutUIStore: { awardAnims, removeAward }, 11 | } = useStore(); 12 | 13 | return ( 14 |
15 | {awardAnims.map((anim) => { 16 | return ( 17 | { 22 | removeAward(anim.id); 23 | }}> 24 | ); 25 | })} 26 | {awardAnims.map((anim) => { 27 | return ; 28 | })} 29 |
30 | ); 31 | }); 32 | export default Award; 33 | -------------------------------------------------------------------------------- /src/infra/hooks/utilites.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from 'react'; 2 | 3 | export const useInterval = (fun: CallableFunction, delay: number, start: boolean) => { 4 | const myRef = useRef(null); 5 | useEffect(() => { 6 | myRef.current = fun; 7 | }, [fun]); 8 | useEffect(() => { 9 | const timer = setInterval(() => { 10 | myRef.current(timer); 11 | }, delay); 12 | return () => clearInterval(timer); 13 | }, [start, delay]); 14 | }; 15 | 16 | export const useTimout = (fun: CallableFunction, delay: number, start: boolean) => { 17 | const myRef = useRef(null); 18 | useEffect(() => { 19 | myRef.current = fun; 20 | }, [fun]); 21 | useEffect(() => { 22 | let timer: null | ReturnType = null; 23 | if (start) { 24 | timer = setTimeout(() => { 25 | myRef.current(timer); 26 | }, delay); 27 | } 28 | return () => { 29 | timer && clearTimeout(timer); 30 | }; 31 | }, [start, delay]); 32 | }; 33 | 34 | export const useEffectOnce = (effect: any) => { 35 | useEffect(effect, []); 36 | }; 37 | -------------------------------------------------------------------------------- /src/infra/stores/lecture/index.ts: -------------------------------------------------------------------------------- 1 | import { EduClassroomStore } from 'agora-edu-core'; 2 | import { EduClassroomUIStore } from '../common'; 3 | import { LectureBoardUIStore } from './board'; 4 | import { LectureRosterUIStore } from './roster'; 5 | import { LectureRoomStreamUIStore } from './stream'; 6 | import { LectrueToolbarUIStore } from './toolbar'; 7 | 8 | export class EduLectureUIStore extends EduClassroomUIStore { 9 | constructor(store: EduClassroomStore) { 10 | super(store); 11 | this._streamUIStore = new LectureRoomStreamUIStore(store, this.shareUIStore, this._getters); 12 | this._rosterUIStore = new LectureRosterUIStore(store, this.shareUIStore, this._getters); 13 | this._boardUIStore = new LectureBoardUIStore(store, this.shareUIStore, this._getters); 14 | this._toolbarUIStore = new LectrueToolbarUIStore(store, this.shareUIStore, this._getters); 15 | } 16 | 17 | get streamUIStore() { 18 | return this._streamUIStore as LectureRoomStreamUIStore; 19 | } 20 | 21 | get rosterUIStore() { 22 | return this._rosterUIStore as LectureRosterUIStore; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/share-mobile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | <> 7 | 11 | 12 | ); 13 | 14 | export const viewBox = '0 0 24 24'; 15 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/edit.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | 7 | 13 | 17 | 18 | ); 19 | 20 | export const viewBox = '0 0 16 16'; 21 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/pen-arrow.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | import React from 'react'; 4 | 5 | import { PathOptions } from '../svg-dict'; 6 | 7 | export const path = ({ iconPrimary, penColor }: PathOptions) => 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | export const viewBox = '0 0 22 24'; 16 | -------------------------------------------------------------------------------- /src/ui-kit/components/checkbox/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Meta } from '@storybook/react'; 3 | import { CheckBox } from './'; 4 | 5 | const meta: Meta = { 6 | title: 'Components/CheckBox', 7 | component: CheckBox, 8 | }; 9 | 10 | export const Docs = () => { 11 | const [checked, setChecked] = useState(false); 12 | 13 | return ( 14 | <> 15 |
16 | { 20 | setChecked(!checked); 21 | }} 22 | /> 23 |
24 |
25 | 30 |
31 |
32 | { 36 | setChecked(!checked); 37 | }} 38 | /> 39 |
40 | 41 | ); 42 | }; 43 | 44 | export default meta; 45 | -------------------------------------------------------------------------------- /src/ui-kit/components/progress/index.css: -------------------------------------------------------------------------------- 1 | .bg-download-bg { 2 | background-color: #e3e3f0; 3 | } 4 | 5 | .bg-download-fg { 6 | background-color: #456dfd; 7 | } 8 | 9 | .progress-height { 10 | height: 0.5rem; 11 | border-radius: 0.25rem; 12 | display: flex; 13 | font-size: 0.75rem; 14 | line-height: 1rem; 15 | } 16 | 17 | 18 | 19 | .dialog-progress-container { 20 | box-sizing: border-box; 21 | margin: 0; 22 | padding: 8px; 23 | line-height: 1.5715; 24 | list-style: none; 25 | position: fixed; 26 | top: 45%; 27 | left: 0; 28 | z-index: 1010; 29 | width: 100%; 30 | pointer-events: none; 31 | text-align: center; 32 | } 33 | .dialog-progress-item { 34 | padding: 30px 25px; 35 | background: #ffffff; 36 | box-shadow: 0px 2px 6px 0px rgba(47, 65, 146, 0.15); 37 | border-radius: 12px; 38 | pointer-events: all; 39 | display: inline-block; 40 | } 41 | 42 | .dialog-progress-tip { 43 | font-size: 13px; 44 | font-weight: 400; 45 | color: #191919; 46 | margin-bottom: 20px; 47 | } 48 | .progress-percentage { 49 | font-size: 18px; 50 | color: #357bf6; 51 | line-height: 22px; 52 | } 53 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/video.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | 10 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/review.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/auto-play-failed.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | <> 7 | 8 | 14 | 15 | ); 16 | export const viewBox = '0 0 130 100'; 17 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/star-outline.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | 10 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/infra/capabilities/containers/dialog/video-gallery/pager.css: -------------------------------------------------------------------------------- 1 | .fcr-video-grid-pager { 2 | } 3 | 4 | .fcr-video-grid-pager__prev, 5 | .fcr-video-grid-pager__next { 6 | /* background: rgba(32, 32, 32, 0.5); */ 7 | backdrop-filter: blur(25px); 8 | border-radius: 14px; 9 | position: absolute; 10 | top: 50%; 11 | transform: translateY(-25%); 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | padding-bottom: 5px; 16 | z-index: 110; 17 | @apply fcr-bg-foreground; 18 | } 19 | 20 | .fcr-video-grid-pager__prev { 21 | left: 20px; 22 | } 23 | 24 | .fcr-video-grid-pager__next { 25 | right: 20px; 26 | } 27 | 28 | .fcr-video-grid-pager__button { 29 | backdrop-filter: blur(25px); 30 | border-radius: 14px; 31 | /* background: rgba(47, 47, 47, 0.95); */ 32 | width: 40px; 33 | height: 40px; 34 | display: flex; 35 | justify-content: center; 36 | align-items: center; 37 | cursor: pointer; 38 | @apply fcr-bg-background; 39 | } 40 | .fcr-video-grid-pager__button:hover { 41 | @apply fcr-bg-brand; 42 | } 43 | 44 | .fcr-video-grid-pager__label { 45 | font-size: 12px; 46 | @apply fcr-text-level1; 47 | } 48 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/microphone.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | 13 | ); 14 | 15 | export const viewBox = '0 0 28 28'; 16 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/register.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | 12 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/reset.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | 11 | ); 12 | 13 | export const viewBox = '0 0 24 24'; 14 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/pretest/volume.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useMemo } from 'react'; 2 | 3 | interface VolumeProps { 4 | maxLength?: number; 5 | cursor: number; 6 | peek: number; 7 | } 8 | 9 | export const Volume: FC = ({ maxLength = 12, cursor, peek = 12 }) => { 10 | const activityIndex = useMemo(() => { 11 | const percentage = cursor / peek; 12 | return Math.round(maxLength * percentage); 13 | }, [maxLength, peek, cursor]); 14 | 15 | return ( 16 |
21 | {Array(maxLength) 22 | .fill('agora') 23 | .map((item, index) => ( 24 |
= index + 1 ? 1 : 0.1})`, 30 | transition: 'all 0.3s ease-in' 31 | }} 32 | key={`${item}-${index}`} 33 | /> 34 | ))} 35 |
36 | ); 37 | }; 38 | 39 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/clear.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "experimentalDecorators": true, 5 | "module": "esnext", 6 | "lib": [ 7 | "dom", 8 | "dom.iterable", 9 | "esnext", 10 | "webworker" 11 | ], 12 | "outDir": "lib", 13 | "noImplicitAny": true, 14 | "noImplicitThis": true, 15 | "strictNullChecks": true, 16 | "allowJs": true, 17 | "skipLibCheck": true, 18 | "declaration": true, 19 | "esModuleInterop": true, 20 | "allowSyntheticDefaultImports": true, 21 | "strict": true, 22 | "forceConsistentCasingInFileNames": true, 23 | "noEmitHelpers": true, 24 | "noEmitOnError": false, 25 | "emitDeclarationOnly": true, 26 | "moduleResolution": "node", 27 | "resolveJsonModule": true, 28 | "isolatedModules": true, 29 | "jsx": "react-jsx", 30 | "noFallthroughCasesInSwitch": true, 31 | "baseUrl": ".", 32 | "paths": { 33 | "@classroom/*": ["src/*"], 34 | "agora-classroom-sdk": ["src/infra/api"] 35 | } 36 | }, 37 | "include": [ 38 | "src" 39 | ], 40 | "exclude": [ 41 | "node_modules", 42 | "**/*/*.stories.tsx" 43 | ], 44 | } 45 | -------------------------------------------------------------------------------- /src/ui-kit/components/input-number/index.css: -------------------------------------------------------------------------------- 1 | .fcr-input-number-wrapper { 2 | @apply fcr-text-level1 fcr-border fcr-border-solid fcr-border-divider; 3 | position: relative; 4 | width: 100%; 5 | height: 100%; 6 | min-width: 0; 7 | padding-left: 15px; 8 | font-size: 14px; 9 | line-height: 1.5715; 10 | border-radius: 4px; 11 | transition: all 0.3s; 12 | display: inline-flex; 13 | align-items: center; 14 | width: 150px; 15 | } 16 | .fcr-input-number-wrapper:hover, 17 | .fcr-input-number-focus { 18 | border-color: #357bf6; 19 | } 20 | 21 | .fcr-input-number-wrapper input { 22 | padding: 0; 23 | border: none; 24 | outline: none; 25 | background-color: transparent; 26 | width: 100%; 27 | } 28 | 29 | .fcr-input-number-wrapper input::-webkit-input-placeholder { 30 | /* WebKit browsers */ 31 | color: #7b88a0; 32 | font-size: 14px; 33 | } 34 | 35 | .fcr-input-number-tail { 36 | padding-right: 6px; 37 | } 38 | 39 | .fcr-input-number-add:hover, 40 | .fcr-input-number-sub:hover { 41 | cursor: pointer; 42 | } 43 | 44 | .fcr-input-number-add.disabled polyline, 45 | .fcr-input-number-sub.disabled polyline { 46 | stroke: #a5adbb !important; 47 | } 48 | -------------------------------------------------------------------------------- /src/ui-kit/components/roster/hooks.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { 3 | defaultColumns, 4 | kickOutColumn, 5 | podiumColumn, 6 | grantBoardColumn, 7 | starsColumn, 8 | superviseColumn, 9 | } from './columns'; 10 | import { Column } from './'; 11 | import { SupportedFunction } from '..'; 12 | import { sortBy } from 'lodash'; 13 | 14 | export const useColumns = (functions: SupportedFunction[]) => { 15 | const showKickOut = functions.includes('kick'); 16 | 17 | const cols = useMemo(() => { 18 | const cols = ([] as Column[]).concat(defaultColumns); 19 | if (functions.includes('kick')) { 20 | cols.push(kickOutColumn); 21 | } 22 | if (functions.includes('podium')) { 23 | cols.push(podiumColumn); 24 | } 25 | 26 | if (functions.includes('grant-board')) { 27 | cols.push(grantBoardColumn); 28 | } 29 | 30 | if (functions.includes('stars')) { 31 | cols.push(starsColumn); 32 | } 33 | 34 | // if (functions.includes('supervise-student')) { 35 | // cols.push(superviseColumn); 36 | // } 37 | 38 | return sortBy(cols, ['order']); 39 | }, [showKickOut]); 40 | 41 | return cols; 42 | }; 43 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/normal-signal.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | 4 | import { PathOptions } from '../svg-dict'; 5 | 6 | export const path = (props: PathOptions) => 10 | 11 | export const viewBox = '0 0 1024 1024' -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/triangle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | export const viewBox = '0 0 26 26'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/emoji.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | import React from 'react'; 4 | 5 | import { PathOptions } from '../svg-dict'; 6 | 7 | export const path = (props: PathOptions) => 8 | 9 | 10 | 11 | 12 | 13 | 14 | export const viewBox = '0 0 24 24'; 15 | 16 | -------------------------------------------------------------------------------- /src/ui-kit/components/tabs/index.tsx: -------------------------------------------------------------------------------- 1 | import Tabs, { TabPaneProps, TabsProps } from 'antd/lib/tabs'; 2 | import classNames from 'classnames'; 3 | import React, { FC, PropsWithChildren } from 'react'; 4 | import { SvgIconEnum, SvgImg } from '../svg-img'; 5 | import './index.css'; 6 | 7 | export type ATabsProps = Pick< 8 | TabsProps, 9 | | 'className' 10 | | 'activeKey' 11 | | 'centered' 12 | | 'type' 13 | | 'onChange' 14 | | 'onEdit' 15 | | 'onTabClick' 16 | | 'animated' 17 | | 'moreIcon' 18 | | 'renderTabBar' 19 | | 'items' 20 | >; 21 | 22 | export const ATabs: FC> = ({ 23 | type, 24 | className, 25 | onEdit, 26 | centered, 27 | ...props 28 | }) => { 29 | const { moreIcon = } = props; 30 | 31 | return ( 32 | 33 | ); 34 | }; 35 | 36 | export type ATabPaneProps = Pick; 37 | 38 | export const ATabPane: FC> = ({ className, ...props }) => { 39 | return ; 40 | }; 41 | -------------------------------------------------------------------------------- /src/ui-kit/components/modal/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/react'; 2 | import React, { useState } from 'react'; 3 | import { Button } from '../button'; 4 | import { Modal } from '../modal'; 5 | 6 | const meta: Meta = { 7 | title: 'Components/Modal', 8 | component: Modal, 9 | }; 10 | 11 | type DocsProps = { 12 | title: string; 13 | }; 14 | 15 | 16 | export const Docs = ({ title }: DocsProps) => ( 17 | <> 18 |
19 | test, ]}> 20 |

你确定要下课吗?

21 |
22 |
23 |
24 | test]}> 25 |

试用时间到,教室已解散!

26 |
27 |
28 |
29 | test, ]}> 33 |

课件未能加载成功,您可以点击重新加载重试,或者从云盘中播放课件

34 |
35 |
36 | 37 | ); 38 | 39 | Docs.args = { 40 | title: 'Modal Title', 41 | }; 42 | 43 | 44 | export default meta; 45 | -------------------------------------------------------------------------------- /src/ui-kit/components/placeholder/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta } from '@storybook/react'; 3 | import { Placeholder, CameraPlaceHolder } from '../placeholder'; 4 | 5 | const meta: Meta = { 6 | title: 'Components/Placeholder', 7 | component: Placeholder, 8 | }; 9 | 10 | type DocsProps = { 11 | placeholderDesc: string; 12 | }; 13 | 14 | export const Docs = ({ placeholderDesc }: DocsProps) => ( 15 | <> 16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 | 38 | ); 39 | 40 | Docs.args = { 41 | placeholderDesc: '', 42 | }; 43 | 44 | export default meta; 45 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/vote.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/stage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | 11 | ); 12 | 13 | export const viewBox = '0 0 28 28'; 14 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/pen-rhombus.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = ({ iconPrimary, penColor }: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | export const viewBox = '0 0 22 24'; 14 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/toast/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react'; 2 | import { Toast } from '@classroom/ui-kit'; 3 | import { useStore } from '@classroom/infra/hooks/ui-store'; 4 | import { TransitionGroup, CSSTransition } from 'react-transition-group'; 5 | import './index.css'; 6 | import { ToastType } from '@classroom/infra/stores/common/share'; 7 | 8 | export const ToastContainer = observer(() => { 9 | const { shareUIStore } = useStore(); 10 | const { toastQueue, removeToast } = shareUIStore; 11 | 12 | return ( 13 | 14 | {toastQueue.map((value: ToastType, idx: number) => ( 15 | 16 | { 20 | removeToast(value.id); 21 | }}> 22 | {value.desc} 23 | 24 | 25 | ))} 26 | 27 | ); 28 | }); 29 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/camera-preview/index.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '@classroom/infra/hooks/ui-store'; 2 | import classNames from 'classnames'; 3 | import { observer } from 'mobx-react'; 4 | import { StreamPlayerCameraPlaceholder } from '../stream'; 5 | import { LocalTrackPlayer } from '../stream/track-player'; 6 | 7 | export const CameraPreview = observer(() => { 8 | const { videoGalleryUIStore, streamUIStore } = useStore(); 9 | const { localCameraStream, localPreview } = videoGalleryUIStore; 10 | 11 | const cls = classNames('fcr-w-full fcr-h-full fcr-overflow-hidden', { 12 | 'fcr-invisible': localCameraStream ? localCameraStream.isCameraMuted : false, 13 | }); 14 | 15 | return localPreview && localCameraStream ? ( 16 |
25 | 26 | {!streamUIStore.settingsOpened && } 27 |
28 | ) : null; 29 | }); 30 | 31 | export default CameraPreview; 32 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/stream-window-on.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { PathOptions } from "../svg-dict"; 3 | 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | 12 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta } from '@storybook/react'; 3 | import { SvgIcon } from './'; 4 | import { SvgIconEnum } from './type'; 5 | import './index.css'; 6 | 7 | const meta: Meta = { 8 | title: 'Components/SvgImg', 9 | component: SvgIcon, 10 | }; 11 | 12 | type DocsProps = { 13 | size: number; 14 | color: string; 15 | }; 16 | 17 | const keys = Object.keys(SvgIconEnum); 18 | export const Docs = ({ size, color }: DocsProps) => { 19 | return ( 20 |
21 |

Icon Gallery

22 |
23 | {keys.map((k) => { 24 | return ( 25 |
26 | 32 |
33 | ); 34 | })} 35 |
36 |
37 | ); 38 | }; 39 | 40 | Docs.args = { 41 | size: 100, 42 | color: '', 43 | }; 44 | 45 | export default meta; 46 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/recording.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/triangle-solid-down.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | export const viewBox = '0 0 18 14'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/mic-disabled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { PathOptions } from "../svg-dict" 3 | 4 | export const path = (props: PathOptions) => 5 | 6 | 7 | 8 | 9 | 10 | 11 | export const viewBox = "0 0 24 24" -------------------------------------------------------------------------------- /src/ui-kit/components/placeholder/index.css: -------------------------------------------------------------------------------- 1 | .placeholder { 2 | @apply fcr-w-full fcr-h-full fcr-flex fcr-flex-col fcr-items-center fcr-justify-center; 3 | } 4 | 5 | .placeholder-desc { 6 | margin-top: 46px; 7 | font-size: 13px; 8 | font-weight: 400; 9 | color: #7d8798; 10 | } 11 | 12 | .camera-placeholder { 13 | @apply fcr-bg-background; 14 | overflow: hidden; 15 | position: relative; 16 | height: 100%; 17 | width: 100%; 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | flex-direction: column; 22 | } 23 | 24 | .camera-placeholder span { 25 | color: #677386; 26 | font-size: 12px; 27 | } 28 | 29 | .pretest .camera-placeholder { 30 | position: absolute; 31 | } 32 | 33 | .maxiumn-wrap { 34 | position: relative; 35 | display: flex; 36 | justify-content: center; 37 | align-items: center; 38 | width: 100%; 39 | height: 100%; 40 | background: #000; 41 | font-size: 13px; 42 | font-weight: 400; 43 | color: #ffffff; 44 | } 45 | 46 | .board-placeholder { 47 | display: flex; 48 | flex-direction: column; 49 | justify-content: center; 50 | align-items: center; 51 | width: 100%; 52 | height: 100%; 53 | } 54 | .reconnect-btn { 55 | margin-top: 30px; 56 | font-size: 16px; 57 | width: 136px; 58 | height: 44px; 59 | } 60 | -------------------------------------------------------------------------------- /src/ui-kit/components/toast/index.css: -------------------------------------------------------------------------------- 1 | .toast { 2 | min-width: 100px; 3 | height: 34px; 4 | 5 | font-size: 14px; 6 | box-shadow: 0px 3px 8px 0px rgba(0, 0, 0, 0.1); 7 | border-radius: 4px; 8 | padding: 0 14px; 9 | animation: toastAnimation 0.2s; 10 | transition: top 0.3s, opacity 0.1s; 11 | @apply fcr-text-white fcr-cursor-pointer fcr-flex fcr-items-center fcr-justify-center fcr-box-border fcr-relative; 12 | } 13 | 14 | @keyframes toastAnimation { 15 | 0% { 16 | transform: scale(1); 17 | opacity: 0; 18 | } 19 | 50% { 20 | transform: scale(1.2); 21 | opacity: 0.5; 22 | } 23 | 100% { 24 | transform: scale(1); 25 | opacity: 1; 26 | } 27 | } 28 | .toast svg { 29 | margin-right: 6px; 30 | width: 21px; 31 | height: 21px; 32 | } 33 | 34 | .toast-success { 35 | @apply fcr-bg-safe fcr-border-safe fcr-border; 36 | } 37 | 38 | .toast-error { 39 | @apply fcr-bg-error fcr-border-error fcr-border; 40 | } 41 | 42 | .toast-warning { 43 | @apply fcr-bg-warning fcr-border-warning fcr-border; 44 | } 45 | 46 | .toast .toast-progress { 47 | position: absolute; 48 | width: 100%; 49 | height: 3px; 50 | background-color: #ccc; 51 | bottom: 0; 52 | left: 0; 53 | } 54 | .toast .toast-current { 55 | height: 3px; 56 | background-color: red; 57 | } 58 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/video-gallery.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | 12 | ); 13 | 14 | export const viewBox = '0 0 24 24'; 15 | -------------------------------------------------------------------------------- /src/ui-kit/components/select/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Meta } from '@storybook/react'; 3 | import { Select } from '../select'; 4 | 5 | const meta: Meta = { 6 | title: 'Components/Select', 7 | component: Select, 8 | }; 9 | 10 | export const Docs = () => { 11 | const options = [ 12 | { value: 'chocolate', label: 'Chocolate' }, 13 | { value: 'strawberry', label: 'Strawberry' }, 14 | { value: 'vanilla', label: 'Vanilla' }, 15 | ]; 16 | const [selectedOption, setSelectedOption] = useState(''); 17 | return ( 18 | <> 19 |
20 | { 34 | console.log(value); 35 | setSelectedOption(value); 36 | }} 37 | options={options} 38 | prefix={food} 39 | /> 40 |
41 | 42 | ); 43 | }; 44 | 45 | export default meta; 46 | -------------------------------------------------------------------------------- /src/ui-kit/components/toolbar/util.ts: -------------------------------------------------------------------------------- 1 | import { SvgIconEnum } from '../svg-img'; 2 | 3 | export const getPenIcon = (penTool: string) => { 4 | switch (penTool) { 5 | case 'square': 6 | return SvgIconEnum.PEN_SQUARE; 7 | case 'circle': 8 | return SvgIconEnum.PEN_CIRCLE; 9 | case 'line': 10 | return SvgIconEnum.PEN_LINE; 11 | case 'arrow': 12 | return SvgIconEnum.PEN_ARROW; 13 | case 'pentagram': 14 | return SvgIconEnum.PEN_PENTAGRAM; 15 | case 'rhombus': 16 | return SvgIconEnum.PEN_RHOMBUS; 17 | case 'triangle': 18 | return SvgIconEnum.PEN_TRIANGLE; 19 | case 'pen': 20 | default: 21 | return SvgIconEnum.PEN_CURVE; 22 | } 23 | }; 24 | 25 | export const getPenShapeIcon = (penTool: string) => { 26 | switch (penTool) { 27 | case 'square': 28 | return SvgIconEnum.SQUARE; 29 | case 'circle': 30 | return SvgIconEnum.CIRCLE; 31 | case 'line': 32 | return SvgIconEnum.LINE; 33 | case 'arrow': 34 | return SvgIconEnum.ARROW; 35 | case 'pentagram': 36 | return SvgIconEnum.PENTAGRAM; 37 | case 'rhombus': 38 | return SvgIconEnum.RHOMBUS; 39 | case 'triangle': 40 | return SvgIconEnum.TRIANGLE; 41 | case 'pen': 42 | default: 43 | return SvgIconEnum.PEN; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/on-podium.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PathOptions } from "../svg-dict" 3 | 4 | export const path = (props: PathOptions) => 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | export const viewBox = "0 0 22 22" -------------------------------------------------------------------------------- /src/ui-kit/components/volume/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Meta } from '@storybook/react'; 3 | import { Volume, AudioVolume } from '../volume'; 4 | 5 | const meta: Meta = { 6 | title: 'Components/Volume', 7 | component: Volume, 8 | }; 9 | 10 | type DocsProps = { 11 | width: number; 12 | height: number; 13 | currentVolume: number; 14 | maxLength: number; 15 | }; 16 | 17 | export const Docs = () => { 18 | const [currentVolume, setCurrentVolume] = useState(0); 19 | let timer; 20 | useEffect(() => { 21 | timer = setInterval(() => { 22 | const number = (Math.random() * 100) | 0; 23 | setCurrentVolume(number); 24 | }, 1000); 25 | return () => { 26 | clearInterval(timer); 27 | }; 28 | }, []); 29 | return ( 30 | <> 31 |
32 | no volume 33 | 34 |
35 |
36 | has volume 37 | 38 |
39 |
40 | isMicMuted 41 | 42 |
43 | 44 | ); 45 | }; 46 | 47 | Docs.args = { 48 | width: 3, 49 | height: 12, 50 | currentVolumn: 0, 51 | maxLength: 20, 52 | }; 53 | 54 | export default meta; 55 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/hand.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/pentagram.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | export const viewBox = '0 0 26 26' -------------------------------------------------------------------------------- /src/infra/stores/common/roster/type.ts: -------------------------------------------------------------------------------- 1 | import { EduRoleTypeEnum } from 'agora-edu-core'; 2 | 3 | export enum DeviceState { 4 | // 设备开启 5 | enabled, 6 | // 设备关闭 7 | disabled, 8 | // 设备不可用 9 | unavailable, 10 | // 设备禁用 11 | unauthorized, 12 | } 13 | 14 | export type Operation = 15 | | 'podium' 16 | | 'grant-board' 17 | | 'camera' 18 | | 'microphone' 19 | | 'kick' 20 | | 'chat' 21 | | 'star' 22 | | 'supervise-student'; 23 | 24 | export type Operations = Partial>; 25 | 26 | export type Profile = { 27 | uid: string | number; 28 | isOnPodium: boolean; 29 | cameraState: DeviceState; 30 | microphoneState: DeviceState; 31 | }; 32 | 33 | /** 34 | * 分页查询用户参数 35 | */ 36 | export interface FetchUserParam { 37 | /** 38 | * 下一页的ID 39 | */ 40 | nextId: string | number | null | undefined; 41 | /** 42 | * 一页查询多少条 43 | */ 44 | count: number; 45 | /** 46 | * 筛选类型 0:全部 1:禁言 47 | */ 48 | type: FetchUserType; 49 | /** 50 | * 查询角色 51 | */ 52 | role: EduRoleTypeEnum; 53 | /** 54 | * 查询的用户名称,模糊查询 55 | */ 56 | userName?: string; 57 | } 58 | 59 | /** 60 | * 筛选用户类型 0:全部 1:禁言 61 | */ 62 | export enum FetchUserType { 63 | /** 64 | * 筛选全部的用户 65 | */ 66 | all = '0', 67 | /** 68 | * 筛选禁言的用户 69 | */ 70 | mute = '1', 71 | } 72 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/camera-off-mobile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = ({ iconPrimary }: PathOptions) => ( 6 | <> 7 | 13 | 19 | 20 | ); 21 | 22 | export const viewBox = '0 0 40 40'; 23 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/hand-up/invite-confirm.css: -------------------------------------------------------------------------------- 1 | .inviteConfirm .content { 2 | text-align: center; 3 | font-size: 13px; 4 | font-weight: 400; 5 | @apply fcr-text-level2; 6 | line-height: 18px; 7 | width: 208px; 8 | margin: 0 auto; 9 | padding: 4px 0; 10 | } 11 | 12 | .inviteConfirm.h5 { 13 | width: 270px; 14 | border-radius: 12px; 15 | background-color: #fff; 16 | box-shadow: 0px 0px 2000px 2000px rgb(0 0 0 / 24%); 17 | } 18 | 19 | .inviteConfirm.h5 .title { 20 | line-height: 3; 21 | text-align: center; 22 | font-size: 17px; 23 | padding-top: 5px; 24 | } 25 | 26 | .inviteConfirm.h5 .content { 27 | padding-bottom: 26px; 28 | } 29 | 30 | .inviteConfirm.h5 .footer { 31 | display: flex; 32 | border-top: 1px solid rgba(0, 0, 0, 0.12); 33 | } 34 | 35 | .inviteConfirm.h5 .footer .button { 36 | width: 50%; 37 | width: 50%; 38 | font-size: 17px; 39 | text-align: center; 40 | line-height: 2.7; 41 | } 42 | 43 | .inviteConfirm.h5 .footer .button:hover { 44 | color: #357bf6; 45 | } 46 | 47 | .inviteConfirm.h5 .footer .button:first-child { 48 | border-right: 1px solid rgba(0, 0, 0, 0.12); 49 | } 50 | 51 | .inviteConfirm.h5 .footer .button.disable, 52 | .inviteConfirm.h5 .footer .button.disable :hover { 53 | color: rgb(187, 187, 187); 54 | pointer-events: none; 55 | cursor: not-allowed; 56 | } 57 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/pen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | 10 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/rhombus.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | export const viewBox = '0 0 26 26'; -------------------------------------------------------------------------------- /src/infra/utils/async-queue.ts: -------------------------------------------------------------------------------- 1 | import { bound, Log, Logger } from 'agora-rte-sdk'; 2 | import sortBy from 'lodash/sortBy'; 3 | 4 | export enum WorkPriority { 5 | high, 6 | normal, 7 | low, 8 | } 9 | 10 | type AsyncWork = { 11 | priority: WorkPriority; 12 | run: () => Promise; 13 | fail: (e: Error) => void; 14 | }; 15 | 16 | @Log.attach() 17 | class AsyncQueue { 18 | logger!: Logger; 19 | private _handle?: NodeJS.Timeout; 20 | private _queue: AsyncWork[] = []; 21 | 22 | runNextTick(run: () => Promise, fail: (e: Error) => void, priority = WorkPriority.normal) { 23 | this._queue.push({ run, fail, priority }); 24 | 25 | this.sortWorks(); 26 | 27 | if (this._handle) { 28 | clearTimeout(this._handle); 29 | } 30 | this._handle = setTimeout(this._execute); 31 | } 32 | 33 | sortWorks() { 34 | this._queue = sortBy(this._queue, ['priority']); 35 | } 36 | 37 | @bound 38 | private async _execute() { 39 | const copy = [...this._queue]; 40 | this._queue = []; 41 | 42 | for (let i = 0; i < copy.length; i++) { 43 | const { run, fail } = copy[i]; 44 | try { 45 | await run(); 46 | } catch (e) { 47 | fail(e as Error); 48 | break; 49 | } 50 | } 51 | this.logger.info('async works done'); 52 | } 53 | } 54 | 55 | export default new AsyncQueue(); 56 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/pretest/assets/modal-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/infra/contexts/ui-store-factory.ts: -------------------------------------------------------------------------------- 1 | import { EduClassroomStore, EduRoomTypeEnum } from 'agora-edu-core'; 2 | import { EduClassroomUIStore } from '../stores/common'; 3 | import { EduInteractiveUIClassStore } from '../stores/interactive'; 4 | import { EduLectureUIStore } from '../stores/lecture'; 5 | import { EduLectureH5UIStore } from '../stores/lecture-mobile'; 6 | import { Edu1v1ClassUIStore } from '../stores/one-on-one'; 7 | 8 | export class EduUIStoreFactory { 9 | static createWithType(roomType: EduRoomTypeEnum, store: EduClassroomStore): EduClassroomUIStore { 10 | switch (roomType) { 11 | case EduRoomTypeEnum.Room1v1Class: 12 | return new Edu1v1ClassUIStore(store); 13 | case EduRoomTypeEnum.RoomSmallClass: 14 | return new EduInteractiveUIClassStore(store); 15 | case EduRoomTypeEnum.RoomBigClass: 16 | return new EduLectureUIStore(store); 17 | default: 18 | throw new Error(`No supported UIStore for room type: ${roomType}`); 19 | } 20 | } 21 | static createWithTypeH5( 22 | roomType: EduRoomTypeEnum, 23 | store: EduClassroomStore, 24 | ): EduClassroomUIStore { 25 | switch (roomType) { 26 | case EduRoomTypeEnum.RoomBigClass: 27 | return new EduLectureH5UIStore(store); 28 | default: 29 | throw new Error(`No supported UIStore for room type: ${roomType}`); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/infra/capabilities/containers/roster/user-list.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { observer } from 'mobx-react'; 3 | import { useStore } from '@classroom/infra/hooks/ui-store'; 4 | import { Roster, RosterTable } from '@classroom/ui-kit'; 5 | 6 | export type RosterContainerProps = { 7 | onClose: () => void; 8 | }; 9 | 10 | export const RosterContainer: FC = observer(({ onClose }) => { 11 | const { rosterUIStore } = useStore(); 12 | const { 13 | teacherName, 14 | searchKeyword, 15 | setSearchKeyword, 16 | rosterFunctions: functions, 17 | carouselProps, 18 | uiOverrides, 19 | } = rosterUIStore; 20 | 21 | const { width } = uiOverrides; 22 | return ( 23 | 32 | 33 | 34 | ); 35 | }); 36 | 37 | const RosterTableContainer: FC = observer(() => { 38 | const { rosterUIStore } = useStore(); 39 | const { rosterFunctions: functions, userList, clickRowAction } = rosterUIStore; 40 | 41 | return ; 42 | }); 43 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/camera-enabled.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { PathOptions } from "../svg-dict" 3 | 4 | export const path = (props: PathOptions) => 5 | 6 | 7 | 8 | 9 | 10 | 11 | export const viewBox = "0 0 24 24" -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/log.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | 10 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/placeholder-no-setup.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 13 | 14 | 15 | 16 | export const viewBox = '0 0 90 90'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/triangle-solid-up.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | export const viewBox = '0 0 18 14'; -------------------------------------------------------------------------------- /src/infra/capabilities/containers/stream/assets/svga/video-play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 开始 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/settings.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | 10 | ); 11 | 12 | export const viewBox = '0 0 14 14'; 13 | -------------------------------------------------------------------------------- /src/infra/stores/lecture-mobile/index.ts: -------------------------------------------------------------------------------- 1 | import { EduClassroomStore } from 'agora-edu-core'; 2 | import { LectureH5BoardUIStore } from './board'; 3 | import { LectureH5RoomStreamUIStore } from './stream'; 4 | import { LectureH5LayoutUIStore } from './layout'; 5 | import { EduClassroomUIStore } from '../common'; 6 | import { LectureH5DeviceSettingUIStore } from './device-setting'; 7 | 8 | export class EduLectureH5UIStore extends EduClassroomUIStore { 9 | constructor(store: EduClassroomStore) { 10 | super(store); 11 | this._streamUIStore = new LectureH5RoomStreamUIStore(store, this.shareUIStore, this._getters); 12 | this._boardUIStore = new LectureH5BoardUIStore(store, this.shareUIStore, this._getters); 13 | this._layoutUIStore = new LectureH5LayoutUIStore(store, this.shareUIStore, this._getters); 14 | this._deviceSettingUIStore = new LectureH5DeviceSettingUIStore( 15 | store, 16 | this.shareUIStore, 17 | this._getters, 18 | ); 19 | } 20 | 21 | get streamUIStore() { 22 | return this._streamUIStore as LectureH5RoomStreamUIStore; 23 | } 24 | 25 | get boardUIStore() { 26 | return this._boardUIStore as LectureH5BoardUIStore; 27 | } 28 | 29 | get layoutUIStore() { 30 | return this._layoutUIStore as LectureH5LayoutUIStore; 31 | } 32 | get deviceSettingUIStore() { 33 | return this._deviceSettingUIStore as LectureH5DeviceSettingUIStore; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/triangle-solid-right.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | export const viewBox = '0 0 18 18'; -------------------------------------------------------------------------------- /src/infra/utils/event-center.ts: -------------------------------------------------------------------------------- 1 | import { AGEventEmitter } from 'agora-rte-sdk'; 2 | 3 | export const AgoraEduClassRoomUIType = 'classroom-ui-events'; 4 | 5 | export enum AgoraEduClassroomUIEvent { 6 | offStreamWindow = 'off-stream-window', 7 | toggleTeacherStreamWindow = 'toggle-teacher-stream-window', 8 | toggleWhiteboard = 'toggle-whiteboard', 9 | dragFileOverBoard = 'drag-file-over-board', 10 | dropFileOnBoard = 'drop-file-on-board', 11 | } 12 | 13 | type EventCallback = (type: AgoraEduClassroomUIEvent, ...args: any[]) => void; 14 | 15 | export class EduEventUICenter extends AGEventEmitter { 16 | static shared: EduEventUICenter = new EduEventUICenter(); 17 | private _callbacks: Set = new Set(); 18 | constructor() { 19 | super(); 20 | } 21 | 22 | emitClassroomUIEvents(type: AgoraEduClassroomUIEvent, ...args: any[]) { 23 | this.emit(AgoraEduClassRoomUIType, type, ...args); 24 | } 25 | 26 | onClassroomUIEvents(cb: EventCallback) { 27 | if (this._callbacks.has(cb)) { 28 | return; 29 | } 30 | this._callbacks.add(cb); 31 | this.on(AgoraEduClassRoomUIType, cb); 32 | } 33 | 34 | offClassroomUIEvents(cb: EventCallback) { 35 | this._callbacks.delete(cb); 36 | this.off(AgoraEduClassRoomUIType, cb); 37 | } 38 | 39 | cleanup() { 40 | this._callbacks.forEach((cb) => { 41 | this.offClassroomUIEvents(cb); 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/excel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | 10 | 11 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/infra/capabilities/containers/toolbar/board-cleaners/index.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react'; 2 | import { useStore } from '@classroom/infra/hooks/ui-store'; 3 | import { SvgImg, BoardCleaners, SvgIconEnum } from '@classroom/ui-kit'; 4 | import { useI18n } from 'agora-common-libs'; 5 | import { InteractionStateColors } from '@classroom/ui-kit/utilities/state-color'; 6 | 7 | export const BoardCleanersContainer = observer(() => { 8 | const { toolbarUIStore } = useStore(); 9 | const t = useI18n(); 10 | const { boardCleanerItems, handleBoradCleaner, activeTool } = toolbarUIStore; 11 | 12 | const mappedItems = boardCleanerItems.map((item) => { 13 | const { id, iconType, name } = item; 14 | const isActive = activeTool === id; 15 | 16 | return { 17 | id, 18 | icon: iconType ? ( 19 | 24 | ) : ( 25 | 26 | ), 27 | name, 28 | }; 29 | }); 30 | 31 | return ( 32 | 41 | ); 42 | }); 43 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/delete.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | 10 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/goonstage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => ( 6 | 7 | 13 | 14 | ); 15 | 16 | export const viewBox = '0 0 20 20'; 17 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/speaker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 9 | 10 | export const viewBox = '0 0 1024 1024'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/clicker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | 12 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/switch-screen-share.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 13 | 14 | 15 | 20 | 26 | 27 | 28 | 29 | export const viewBox = '0 0 26 26'; -------------------------------------------------------------------------------- /src/ui-kit/components/util/motion.ts: -------------------------------------------------------------------------------- 1 | import { CSSMotionProps, MotionEventHandler, MotionEndEventHandler } from 'rc-motion'; 2 | 3 | // ================== Collapse Motion ================== 4 | const getCollapsedHeight: MotionEventHandler = () => ({ 5 | height: 0, 6 | opacity: 0, 7 | }); 8 | const getRealHeight: MotionEventHandler = (node) => ({ 9 | height: node.scrollHeight, 10 | opacity: 1, 11 | }); 12 | const getCurrentHeight: MotionEventHandler = (node) => ({ 13 | height: node.offsetHeight, 14 | }); 15 | const skipOpacityTransition: MotionEndEventHandler = (_, event) => 16 | (event as TransitionEvent).propertyName === 'height'; 17 | 18 | const collapseMotion: CSSMotionProps = { 19 | motionName: 'ant-motion-collapse', 20 | onAppearStart: getCollapsedHeight, 21 | onEnterStart: getCollapsedHeight, 22 | onAppearActive: getRealHeight, 23 | onEnterActive: getRealHeight, 24 | onLeaveStart: getCurrentHeight, 25 | onLeaveActive: getCollapsedHeight, 26 | onAppearEnd: skipOpacityTransition, 27 | onEnterEnd: skipOpacityTransition, 28 | onLeaveEnd: skipOpacityTransition, 29 | motionDeadline: 500, 30 | }; 31 | 32 | const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => { 33 | if (transitionName !== undefined) { 34 | return transitionName; 35 | } 36 | return `${rootPrefixCls}-${motion}`; 37 | }; 38 | export { getTransitionName }; 39 | export default collapseMotion; 40 | -------------------------------------------------------------------------------- /src/ui-kit/components/svg-img/paths/hands-up.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PathOptions } from '../svg-dict'; 4 | 5 | export const path = (props: PathOptions) => 6 | 7 | 8 | 9 | 10 | 11 | export const viewBox = '0 0 24 24'; -------------------------------------------------------------------------------- /src/infra/capabilities/containers/toast/index.mobile.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '@classroom/infra/hooks/ui-store'; 2 | import { Scheduler } from 'agora-rte-sdk'; 3 | import { observer } from 'mobx-react'; 4 | import { useEffect, useRef, useState } from 'react'; 5 | import { ComponentLevelRulesMobile } from '../../config'; 6 | import './index.mobile.css'; 7 | export const ToastContainerMobile = observer(() => { 8 | const { 9 | shareUIStore: { toastQueue, isLandscape }, 10 | } = useStore(); 11 | const currToast = toastQueue[Math.max(toastQueue.length - 1, 0)]; 12 | 13 | const [visible, setVisible] = useState(false); 14 | const delayTaskRef = useRef(null); 15 | useEffect(() => { 16 | if (currToast) { 17 | delayTaskRef.current?.stop(); 18 | setVisible(true); 19 | delayTaskRef.current = Scheduler.shared.addDelayTask(() => { 20 | setVisible(false); 21 | }, 3000); 22 | } 23 | }, [toastQueue, currToast]); 24 | return ( 25 |
33 |
34 | {currToast?.desc} 35 |
36 |
37 | ); 38 | }); 39 | -------------------------------------------------------------------------------- /src/ui-kit/components/slider/index.tsx: -------------------------------------------------------------------------------- 1 | import Slider, { SliderSingleProps } from 'antd/lib/slider'; 2 | import classnames from 'classnames'; 3 | import { FC } from 'react'; 4 | import { BaseProps } from '../util/type'; 5 | import './index.css'; 6 | type tooltipPositionProps = 'top' | 'bottom' | ''; 7 | export interface ASliderProps extends BaseProps { 8 | defaultValue?: number; 9 | value?: number; 10 | disabled?: boolean; 11 | max?: number; 12 | min?: number; 13 | step?: number; 14 | tooltipPosition?: tooltipPositionProps; 15 | onChange?: (value: number) => void; 16 | } 17 | 18 | export const ASlider: FC> = ({ 19 | defaultValue = 0, 20 | value = 0, 21 | disabled = false, 22 | max = 100, 23 | min = 0, 24 | step = 1, 25 | tooltipPosition = 'bottom', 26 | onChange = (value: number) => { 27 | console.log(value); 28 | }, 29 | className, 30 | vertical, 31 | ...restProps 32 | }) => { 33 | const cls = classnames({ 34 | [`fcr-theme`]: 1, 35 | [`slider`]: 1, 36 | [`${className}`]: !!className, 37 | }); 38 | return ( 39 |
40 | 50 |
51 | ); 52 | }; 53 | --------------------------------------------------------------------------------