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 |
19 |
22 |
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 |
30 |
31 |
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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------