40 |
{'Notebooks'}
41 |
48 | {(this.props.users != null) && this.props.users.map(user => (
49 |
50 |
51 |
52 | ))}
53 |
54 |
55 | )
56 | }
57 | }
58 |
59 | Content.propTypes = {
60 | classes: PropTypes.object.isRequired,
61 | };
62 |
63 | export default withStyles(styles)(Content);
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
23 |
Classroom
24 |
31 |
32 |
33 |
36 |
37 |
38 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/Components/Classroom_Components/GroupingMenu/Card/Card.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { DragSource } from 'react-dnd'
4 | import { getEmptyImage } from 'react-dnd-html5-backend'
5 |
6 | const dragSource = {
7 | beginDrag(props) {
8 | return {
9 | ...props,
10 | }
11 | }
12 | }
13 |
14 | function dragCollect(connect, monitor) {
15 | return {
16 | connectDragSource: connect.dragSource(),
17 | connectDragPreview: connect.dragPreview(),
18 | isDragging: monitor.isDragging()
19 | }
20 | }
21 |
22 | class Card extends React.Component {
23 | componentDidMount() {
24 | const { connectDragPreview } = this.props
25 | if (connectDragPreview) {
26 | // Use empty image as a drag preview so browsers don't draw it
27 | // and we can draw whatever we want on the custom drag layer instead.
28 | connectDragPreview(getEmptyImage(), {
29 | // IE fallback: specify that we'd rather screenshot the node
30 | // when it already knows it's being dragged so we can hide it with CSS.
31 | captureDraggingState: true,
32 | })
33 | }
34 | }
35 |
36 | render() {
37 | const {
38 | name,
39 | empty,
40 | isDragging, // Injected by React DnD
41 | connectDragPreview, // Injected by React DnD
42 | connectDragSource, // Injected by React DnD
43 | } = this.props
44 |
45 |
46 | return connectDragSource(
47 |
54 | { name }
55 |
56 | )
57 | }
58 | }
59 |
60 | Card.propTypes = {
61 | name: PropTypes.string,
62 | empty: PropTypes.bool,
63 |
64 | // Injected by React DnD
65 | isDragging: PropTypes.bool.isRequired,
66 | connectDragSource: PropTypes.func.isRequired,
67 | }
68 |
69 | Card.defaultProps = {
70 | }
71 |
72 | export default DragSource('CONNECT_CARD', dragSource, dragCollect)(Card)
73 |
--------------------------------------------------------------------------------
/src/Components/Content_Components/tools/Pencil.js:
--------------------------------------------------------------------------------
1 | import { v4 } from 'uuid';
2 |
3 | export const TOOL_PENCIL = 'pencil';
4 |
5 | export default (props) => {
6 | let stroke = null;
7 |
8 | const onMouseDown = (x, y, color, size) => {
9 | stroke = {
10 | id: v4(),
11 | tool: TOOL_PENCIL,
12 | color,
13 | size,
14 | points: [{ x, y }]
15 | };
16 | return stroke;
17 | };
18 |
19 | const drawLine = (item, start, { x, y }) => {
20 | props.lineJoin = 'round';
21 | props.lineCap = 'round';
22 | props.beginPath();
23 | props.lineWidth = item.size;
24 | props.strokeStyle = item.color;
25 | props.globalCompositeOperation = 'source-over'; //or 'destination-over'
26 | props.moveTo(start.x, start.y);
27 | props.lineTo(x, y);
28 | props.closePath();
29 | props.stroke();
30 | };
31 |
32 | const onMouseMove = (x, y) => {
33 | if (!stroke) return [];
34 | const newPoint = { x, y };
35 | const start = stroke.points.slice(-1)[0];
36 | drawLine(stroke, start, newPoint);
37 | stroke.points.push(newPoint);
38 | // return [stroke];
39 | };
40 |
41 | const onMouseUp = (x, y) => {
42 | if (!stroke) return;
43 | onMouseMove(x, y);
44 | const item = stroke;
45 | stroke = null;
46 | return item;
47 | };
48 |
49 | const draw = (item, animated) => {
50 | let time = 0;
51 | let i = 0;
52 | const j = item.points.length;
53 | for (i, j; i < j; i++) {
54 | if (!item.points[i - 1]) continue;
55 | if (animated) {
56 | setTimeout(drawLine.bind(null, item, item.points[i - 1], item.points[i]), time);
57 | time += 10;
58 | } else {
59 | drawLine(item, item.points[i - 1], item.points[i]);
60 | }
61 | }
62 | };
63 |
64 | return {
65 | onMouseDown,
66 | onMouseMove,
67 | onMouseUp,
68 | draw,
69 | };
70 | }
71 |
--------------------------------------------------------------------------------
/src/Components/Content_Components/UserCardSmall.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import PropTypes from 'prop-types'
3 | import { withStyles } from '@material-ui/core/styles'
4 | import { Card } from '@material-ui/core'
5 | import LocalStream from '../Classroom_Components/LocalStream'
6 | import RemoteStream from '../Classroom_Components/RemoteStream'
7 |
8 | const styles = theme => ({
9 | card: {
10 | // width: 92,
11 | // height: 80,
12 | width: 460 / 5,
13 | height: 300 / 5 + 9
14 | },
15 | container: {
16 | display: 'flex',
17 | flexDirection: 'column',
18 | justifyContent: 'center',
19 | justifyItems: 'center',
20 | }
21 | })
22 |
23 | class UserCardSmall extends React.Component {
24 | constructor(props) {
25 | super(props);
26 | this.state = {
27 | animate: true,
28 | self: null,
29 | drawRight: 'Read Only',
30 | camOpen: true
31 | };
32 | }
33 |
34 | disableWebcam() {
35 | this.setState({ camOpen: false })
36 | }
37 |
38 | render() {
39 | const { user } = this.props;
40 | const { classes, ...other } = this.props;
41 | return (
42 |
43 | {user}
44 |
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | function Webcam(props) {
53 | const { self, user } = props
54 | if (user === self) {
55 | return (
56 |
61 | )
62 | } else {
63 | return (
64 |
71 | )
72 | }
73 | }
74 |
75 | export default withStyles(styles)(UserCardSmall);
--------------------------------------------------------------------------------
/src/Components/NotificationBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { withStyles } from '@material-ui/core/styles';
4 | // import Button from '@material-ui/core/Button';
5 | import Snackbar from '@material-ui/core/Snackbar';
6 | import IconButton from '@material-ui/core/IconButton';
7 | import CloseIcon from '@material-ui/icons/Close';
8 |
9 | const styles = theme => ({
10 | close: {
11 | padding: theme.spacing.unit / 2,
12 | },
13 | });
14 |
15 | class NotificationBar extends React.Component {
16 | render() {
17 | const { classes } = this.props;
18 | return (
19 |
20 | {this.props.message}}
33 | action={[
34 | //,
37 |
44 |
45 | ,
46 | ]}
47 | />
48 |
49 | );
50 | }
51 | }
52 |
53 | NotificationBar.propTypes = {
54 | classes: PropTypes.object.isRequired,
55 | };
56 |
57 | export default withStyles(styles)(NotificationBar);
--------------------------------------------------------------------------------
/src/Components/Classroom_Components/GroupingMenu/ManageGroup.js:
--------------------------------------------------------------------------------
1 | import Popover from '../../Popover'
2 | import React, { Fragment } from 'react';
3 | import { DialogContent, Button, withStyles, Typography, Grid} from '@material-ui/core'
4 | import {Stage, Layer} from 'react-konva'
5 | import {connection as conn, genid} from '../../../interface/connection'
6 | import Rectangle from '../Whiteboard_Components/Rectangle';
7 | import Card from './Card'
8 | import App from './App';
9 |
10 | const styles = theme => ({
11 | })
12 |
13 | const generalAttrs = {rotation: 0, scaleX: 1, scaleY: 1}
14 |
15 | function defaultRect() {
16 | return {type: "group", x: 800/2 - 50, y: 600/2 - 50, width: 100, height: 30, fill: '#ffff0080', name: genid(), ...generalAttrs}
17 | }
18 |
19 | class ManageGroup extends React.Component {
20 | stageRef = null
21 | state = {
22 | groups: []
23 | }
24 |
25 | addGroup = () => {
26 | const newGroup = defaultRect()
27 | this.setState({objects: [...this.state.groups, newGroup]})
28 | console.log(this.props.session_user)
29 | }
30 |
31 | render() {
32 | const {classes, ...others} = this.props
33 | return (
34 |
35 |
38 |
39 |
40 | {/*
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | */}
49 |
50 |
51 |
52 | )
53 | }
54 | }
55 |
56 | export default withStyles(styles, {withTheme: true})(ManageGroup);
57 |
--------------------------------------------------------------------------------
/src/Components/Classroom_Components/ClassMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Button, Menu, MenuItem} from '@material-ui/core'
3 | import Leave from '@material-ui/icons/DirectionsRun'
4 | import {withStyles} from '@material-ui/core/styles'
5 | import { connection as conn } from '../../interface/connection';
6 | import { store } from '../..';
7 |
8 | const styles = theme => ({})
9 |
10 | class ClassMenu extends React.Component {
11 | state = {
12 | anchorEl: null,
13 | }
14 |
15 | async leave() {
16 | this.handleClose()
17 | const response = await conn.call("leave_class")
18 | if (response.type === "ok") {
19 | store.dispatch({type: "leaveClass"})
20 | this.props.changeScene(1)
21 | // this.props.handleNotification("leave_class success")
22 | // this.setState({session_user: [], joined: null}) //TODO
23 | } else throw new Error("invalid action: leave class")
24 | }
25 |
26 | handleClick = event => {
27 | this.setState({ anchorEl: event.currentTarget })
28 | }
29 |
30 | handleClose = () => {
31 | this.setState({ anchorEl: null })
32 | }
33 |
34 | render() {
35 | const { anchorEl } = this.state;
36 | // const { classes } = this.props;
37 |
38 | return (
39 |
40 |
49 |
61 |
62 | )
63 | }
64 | }
65 |
66 | export default withStyles(styles)(ClassMenu);
--------------------------------------------------------------------------------
/src/Components/Classroom_Components/GroupingMenu/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { DragDropContext } from 'react-dnd'
3 | import HTML5Backend from 'react-dnd-html5-backend'
4 |
5 | import Card from './Card'
6 | import CardWall from './CardWall'
7 | import CardLayer from './CardLayer'
8 | import {ClassStatusChannel} from '../../../interface/connection'
9 |
10 | import './App.scss'
11 |
12 | class App extends Component {
13 | constructor(props) {
14 | super(props)
15 | this.state = {
16 | groups: ['All', 'Group1', 'Group2', 'Group3']
17 | }
18 | this.updateCardStatus = this.updateCardStatus.bind(this)
19 | }
20 |
21 | updateCardStatus(cardId, targetStatus) {
22 | const cards = this.props.groupCards
23 | const targetIndex = cards.findIndex(c => (cardId === c.id))
24 | cards[targetIndex].status = targetStatus // update card status
25 |
26 | const targetCard = cards.splice(targetIndex, 1)[0] // delete old card
27 | cards.push(targetCard) // insert target card to last position of the array
28 |
29 | const result = ClassStatusChannel.changeGroup(cardId, targetStatus)
30 | this.props.handleNotification(`${cardId} is grouped to ${targetStatus}`)
31 | }
32 |
33 | groupOfCards() {
34 | const cards = this.props.groupCards
35 | const cardsGroup = {}
36 |
37 | cards.forEach((card) => {
38 | if (Array.isArray(cardsGroup[card.status])) {
39 | cardsGroup[card.status].push(card)
40 | } else {
41 | cardsGroup[card.status] = [card]
42 | }
43 | })
44 | return cardsGroup
45 | }
46 |
47 | render() {
48 | const cards = this.groupOfCards()
49 |
50 | return (
51 |
52 |
53 |
54 | {
55 | this.state.groups.map(status => (
56 |
61 | {
62 | (cards[status] || []).map(card => (
63 |
69 | ))
70 | }
71 |
72 | ))
73 | }
74 |
75 |
76 | );
77 | }
78 | }
79 |
80 | export default DragDropContext(HTML5Backend)(App)
81 |
--------------------------------------------------------------------------------
/src/Components/ClassNotificationBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { withStyles } from '@material-ui/core/styles';
4 | // import Button from '@material-ui/core/Button';
5 | import Snackbar from '@material-ui/core/Snackbar';
6 | import IconButton from '@material-ui/core/IconButton';
7 | import CloseIcon from '@material-ui/icons/Close';
8 | import Slide from '@material-ui/core/Slide';
9 |
10 | const styles = theme => ({
11 | close: {
12 | padding: theme.spacing.unit / 2,
13 | },
14 | })
15 |
16 | // function TransitionLeft() {
17 | // return
18 | // }
19 |
20 | class ClassNotificationBar extends React.Component {
21 | state = {
22 | // Transition: null
23 | }
24 | // componentDidMount() {
25 | // this.setState({open: true, TransitionLeft})
26 | // }
27 | render() {
28 | const { classes } = this.props;
29 | return (
30 |
31 | {this.props.message}}
45 | action={[
46 | //,
49 |
56 |
57 | ,
58 | ]}
59 | />
60 |
61 | );
62 | }
63 | }
64 |
65 | ClassNotificationBar.propTypes = {
66 | classes: PropTypes.object.isRequired,
67 | };
68 |
69 | export default withStyles(styles)(ClassNotificationBar);
--------------------------------------------------------------------------------
/src/Components/Classroom_Components/RndContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Rnd} from 'react-rnd'
3 | import {withStyles} from '@material-ui/core/styles'
4 |
5 | const styles = theme => ({})
6 |
7 | class RndContainer extends React.Component {
8 | render() {
9 | const { children, zIndex, id,
10 | position, size, enableResizing, minWidth,
11 | lockAspectRatio, lockAspectRatioExtraHeight
12 | } = this.props
13 | return (
14 |
this.props.bringTop()}
18 | onDragStart={() => this.props.bringTop()}
19 | // TODO need physically click the card header to handle blur problem
20 | // onDragStop={() => {
21 | // setTimeout(() => {
22 | // let {x, y} = document.getElementById(`draggable${id}`).getBoundingClientRect()
23 | // x += 10
24 | // y += 10
25 | // console.log("clicking", x, y)
26 | // const ev = new MouseEvent('click', {
27 | // 'view': window,
28 | // 'bubbles': true,
29 | // 'cancelable': true,
30 | // screenX: x,
31 | // screenY: y
32 | // })
33 | // const el = document.elementFromPoint(x, y)
34 | // el.dispatchEventev
35 | // }, 2000)
36 | // }}
37 | lockAspectRatio={lockAspectRatio || false}
38 | enableResizing={(enableResizing === false)? {} : {
39 | bottom: true, bottomLeft: true, bottomRight: true,
40 | left: true, right: true,
41 | top: true, topLeft: true, topRight: true
42 | }}
43 | lockAspectRatioExtraHeight={lockAspectRatioExtraHeight || 0}
44 | bounds="window"
45 | minWidth={minWidth || 200}
46 | dragHandleClassName={
47 | document.getElementById(`draggable${id}`)?
48 | document.getElementById(`draggable${id}`).className : null
49 | }
50 | ref={el => this.props.inputRef(id, el)}
51 | >
52 | {children}
53 |
54 | )
55 | }
56 | }
57 |
58 | export default withStyles(styles)(RndContainer);
--------------------------------------------------------------------------------
/src/Components/Classroom_Components/GroupingMenu/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Realistic and Interactive Online Classroom (Frontend)
2 |
3 | Demo: [https://overcoded.tk](https://overcoded.tk "Realistic and Interactive Online Classroom")
4 |
5 | Elixir backend Github repo: [https://github.com/herbert1228/online-classroom-elixir-server](https://github.com/herbert1228/online-classroom-elixir-server "Elixir backend")
6 |
7 | ## Update:
8 |
9 | - Grouping
10 | - Teacher can assign students into groups
11 | - Students in the same group can edit a shared whiteboard (work together)
12 |
13 | - Class Management
14 | - Teacher can control the webcam permission of students
15 | - enable/disable audio
16 | - enable/disable video
17 |
18 | ## First stable version with the followings major features:
19 |
20 | - Create classroom session letting students to enroll and join.
21 | - Enter a classroom as Teacher (owner of the classroom).
22 | - Enter a classroom as Student (after enrolling with Teacher's username
23 | and classroom name).
24 | - Desktop like classroom, components are draggable and some are
25 | resizable.
26 | - Classroom components:
27 | - Webcam of teacher and all students
28 | - Peer to Peer connection
29 | - Mute, Stop, Re-call
30 |
31 | - Sync and Shared Whiteboard
32 | - Self-whiteboard is editable
33 | - Other whiteboards are view only
34 | - Sync instantly
35 | - Can view teacher's whiteboard as student Only
36 | - Can view all students' whiteaboard as Teacher
37 | - Edit with:
38 | - Add/remove/drag/resize/rotate: images and texts
39 | - Draw with Pen
40 |

41 |
42 | - Drawer (File system)
43 | - Upload by selecting / dragging
44 | - View image with popup window component
45 | - Delete a file
46 | - Download a file
47 | - Share a file with teacher or students
48 |
49 |
50 | - Connection driver for communicating with an elixir backend hosted at AWS ec2 server
51 | - 3 protocols
52 | - simple call / cast (call is synchronous which returns the value, cast is asynchronous)
53 | - signaling server call / cast
54 | - whiteboard server call / cast
55 | - Websocket secure wss (major)
56 | - Https (upload/download file)
57 | - Also support: ws and http
58 | - STUN/TURN (hosted on our AWS ec2 server) config
59 |
60 | ## Screenshot
61 |
62 | 
63 |
--------------------------------------------------------------------------------
/src/Components/DrawerLeft.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types'
3 | import {Divider, Drawer, withStyles} from '@material-ui/core'
4 | import {ListItems} from './Drawer_Components/DrawerLeftList';
5 | import classNames from 'classnames'
6 | import {drawerWidth} from "./index";
7 | import { connection as conn } from '../interface/connection'
8 | import {store} from '../index'
9 | import {withCookies} from 'react-cookie'
10 | import {compose} from 'redux'
11 | import UserSettings from './Drawer_Components/UserSettings';
12 |
13 | const styles = theme => ({
14 | drawerPaperLeft: {
15 | position: 'fixed',
16 | whiteSpace: 'nowrap',
17 | width: drawerWidth,
18 | background: "rgba(30,30,30,0.15)"
19 | },
20 | toolbar: {
21 | display: 'flex',
22 | alignItems: 'left',
23 | justifyContent: 'flex-start',
24 | padding: '0 8px',
25 | ...theme.mixins.toolbar,
26 | },
27 | avatar: {
28 | backgroundColor: "rgba(50,70,60,0.5)"
29 | },
30 | })
31 |
32 | class DrawerLeft extends React.Component {
33 | state = {
34 | anchorEl: null,
35 | selectedIndex: 1,
36 | }
37 |
38 | handleClickListItem = event => {
39 | this.setState({ anchorEl: event.currentTarget })
40 | }
41 |
42 | handleMenuItemClick = (event, index) => {
43 | this.setState({ selectedIndex: index, anchorEl: null })
44 | }
45 |
46 | handleClose = () => {
47 | this.setState({ anchorEl: null })
48 | }
49 |
50 | handleLogout = async () => {
51 | const result = await conn.call("logout")
52 | if (result.type !== "ok") throw new Error("invalid_logout")
53 | this.props.cookies.remove("name")
54 | this.props.cookies.remove("password")
55 | store.dispatch({type: "logout"})
56 | }
57 |
58 | render() {
59 | const {classes, open, ...other} = this.props //, ...others
60 |
61 | return (
62 |
69 |
70 |
71 |
74 |
75 | )
76 | }
77 | }
78 |
79 | DrawerLeft.propTypes = {
80 | classes: PropTypes.object.isRequired,
81 | }
82 |
83 | export default compose(
84 | withCookies,
85 | withStyles(styles, {withTheme: true}),
86 | )(DrawerLeft)
--------------------------------------------------------------------------------
/src/Components/Drawer_Components/UserSettings.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connection as conn } from '../../interface/connection'
3 | import {store} from '../../index'
4 | import {withCookies} from 'react-cookie'
5 | import {compose} from 'redux'
6 | import {Avatar, withStyles, List, ListItem, ListItemText, Menu, MenuItem} from '@material-ui/core'
7 | import ChangeUserInfo from '../Locations/PopupFunction/ChangeUserInfo'
8 |
9 | const styles = theme => ({
10 | avatar: {
11 | backgroundColor: "rgba(50,70,60,0.5)"
12 | },
13 | })
14 |
15 | class UserSettings extends Component {
16 | state = {
17 | anchorEl: null,
18 | }
19 |
20 | handleClickListItem = event => {
21 | this.setState({ anchorEl: event.currentTarget })
22 | }
23 |
24 | handleClose = () => {
25 | this.setState({ anchorEl: null })
26 | }
27 |
28 | handleLogout = async () => {
29 | const result = await conn.call("logout")
30 | if (result.type !== "ok") throw new Error("invalid_logout")
31 | this.props.cookies.remove("name")
32 | this.props.cookies.remove("password")
33 | store.dispatch({type: "logout"})
34 | }
35 |
36 | render() {
37 | const {classes} = this.props
38 | return (
39 |
40 |
41 |
48 |
49 | {this.props.self.substring(0, 3)}
50 |
51 |
56 |
57 |
58 |
70 |
71 | )
72 | }
73 | }
74 |
75 | export default compose(
76 | withCookies,
77 | withStyles(styles, {withTheme: true}),
78 | )(UserSettings)
--------------------------------------------------------------------------------
/src/Components/Classroom_Components/ViewButton.js:
--------------------------------------------------------------------------------
1 | import Popover from '../Popover'
2 | import React, { Fragment } from 'react';
3 | import {withStyles} from '@material-ui/core/styles'
4 | import { DialogContent, IconButton } from "@material-ui/core";
5 | import {uploadURL} from '../../interface/connection'
6 | import View from '@material-ui/icons/Pageview';
7 |
8 | const styles = theme => ({
9 |
10 | })
11 |
12 | class ViewButton extends React.Component {
13 | state = {
14 | open: false,
15 | data: null,
16 | }
17 |
18 | handleClose = () => {
19 | this.setState({open: false})
20 | }
21 |
22 | handleOpen() {
23 | this.setState({open: true})
24 | }
25 |
26 | handleView = (filename) => {
27 | const {username, password} = this.props
28 | fetch(uploadURL+`/download/${username}/${password}/${filename}`)
29 | .then(response => response.text())
30 | // .then(response => response.blob())
31 | // .then(blob => {
32 | // const reader = new FileReader()
33 | // reader.addEventListener("load", () => {
34 | // this.setState({data: reader.result})
35 | // }, false)
36 |
37 | // reader.readAsDataURL(blob);
38 | // })
39 | .then(() => this.handleOpen())
40 | .catch(e => {this.props.handleNotification(`${e}`)})
41 | }
42 |
43 | cannotView(filename) {
44 | if (filename.substr(-4) === ".jpg"
45 | || filename.substr(-4) === ".png"
46 | || filename.substr(-4) === ".PNG"
47 | || filename.substr(-4) === ".gif"
48 | || filename.substr(-4) === ".svg"
49 | || filename.substr(-4) === ".bmp"
50 | // || filename.substr(-4) === ".pdf"
51 | || filename.substr(-5) === ".jpeg"
52 | || filename.substr(-5) === ".apng"){
53 | return false
54 | }
55 | return true
56 | }
57 |
58 | render() {
59 | const { username, password, filename} = this.props
60 | return (
61 |
62 | this.handleView(filename)}>
66 |
67 |
68 |
69 |
70 |
76 |
77 |
78 |
79 | );
80 | }
81 | }
82 |
83 | export default withStyles(styles, {withTheme: true})(ViewButton)
--------------------------------------------------------------------------------
/src/Components/RegisterBtn.js:
--------------------------------------------------------------------------------
1 | import React, {Fragment} from 'react';
2 | import PopoverWithBtn from './PopoverWithBtn'
3 | import {Button, DialogActions, DialogContent, DialogContentText, TextField} from "@material-ui/core";
4 | import {connection as conn} from '../interface/connection'
5 |
6 | export default class RegisterBtn extends React.Component {
7 | state = {
8 | open: false,
9 | name: "",
10 | password: ""
11 | }
12 |
13 | handle() {
14 | this.setState({open: !this.state.open});
15 | }
16 |
17 | handleName(e) {
18 | this.setState({name: e.target.value});
19 | };
20 |
21 | handlePassword(e) {
22 | this.setState({password: e.target.value});
23 | };
24 |
25 | keyPress(e) {
26 | if (e.keyCode === 13) {
27 | e.preventDefault();
28 | this.handleSubmit()
29 | }
30 | }
31 |
32 | async handleSubmit() {
33 | const result = await conn.call("register", {username: this.state.name, password: this.state.password})
34 | if (result.type === "ok") {
35 | this.props.handleNotification("registered")
36 | } else if (result.type === "reject") {
37 | this.props.handleNotification(result.reason)
38 | }
39 | this.setState({open: false})
40 | }
41 |
42 | render() {
43 | return (
44 |
45 |
46 |
47 |
48 |
49 |
50 | {/*{children}*/}
51 |
52 |
61 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | )
76 | }
77 | }
--------------------------------------------------------------------------------
/src/Components/Locations/PopupFunction/CreateClass.js:
--------------------------------------------------------------------------------
1 | import React, {Fragment} from 'react';
2 | import PopoverWithBtn from '../../PopoverWithBtn'
3 | import {Button, DialogActions, DialogContent, DialogContentText, TextField} from "@material-ui/core";
4 | import { connection as conn } from '../../../interface/connection'
5 | import {store} from '../../../index'
6 |
7 | export default class CreateClass extends React.Component {
8 | state = {
9 | open: false,
10 | class_name: ""
11 | }
12 |
13 | handle() {
14 | this.setState({open: !this.state.open});
15 | }
16 |
17 | handleClassName(e) {
18 | this.setState({class_name: e.target.value});
19 | };
20 |
21 | keyPress(e) {
22 | if (e.keyCode === 13) {
23 | e.preventDefault();
24 | this.handleSubmit()
25 | }
26 | }
27 |
28 | async handleSubmit() {
29 | const response = await conn.call("create_class", {class_name: this.state.class_name})
30 | if (response.type === "ok") {
31 | // [created, enrolled] = Promise.all(conn.call(), conn.call())
32 | const created = await conn.call("get_created_class")
33 | const enrolled = await conn.call("get_enrolled_class")
34 | store.dispatch({
35 | type:"get_created_class",
36 | result: created.result
37 | })
38 | store.dispatch({
39 | type: "get_enrolled_class",
40 | result: enrolled.result
41 | })
42 | this.props.handleNotification("created class success")
43 | }
44 | if (response.type === "reject") {
45 | this.props.handleNotification(`created class failed, reason: ${response.reason}`)
46 | }
47 | this.setState({open: false})
48 | }
49 |
50 | render() {
51 | return (
52 |
53 |
54 |
55 |
56 |
57 |
58 | {/*{children}*/}
59 |
60 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | )
76 | }
77 | }
--------------------------------------------------------------------------------
/src/Components/Classroom_Components/ShareBtn.js:
--------------------------------------------------------------------------------
1 | import React, {Fragment} from 'react';
2 | import PopoverWithBtn from '../PopoverWithBtn'
3 | import {Button, DialogActions, DialogContent, RadioGroup, FormControlLabel, IconButton, Radio} from "@material-ui/core";
4 | import {connection as conn} from '../../interface/connection'
5 | import { Share } from '@material-ui/icons';
6 |
7 | export default class ShareBtn extends React.Component {
8 | state = {
9 | open: false,
10 | value: null,
11 | joined: []
12 | }
13 |
14 | async getStudentsInfo(c) {
15 |
16 | }
17 |
18 | handleOpenOrClose() {
19 | this.setState({open: !this.state.open})
20 | }
21 |
22 | handleShare = async filename => {
23 | if (this.state.value) {
24 | const response = await conn.call("file_share", {share_target: this.state.value, filename})
25 | if (response) {
26 | if (response.result === "ok") this.props.handleNotification(`Shared ${filename} to ${this.state.value}`)
27 | }
28 | }
29 | this.handleOpenOrClose()
30 | }
31 |
32 | handleEntering = () => {
33 | this.radioGroupRef.focus()
34 | }
35 |
36 | handleSelect = (event, value) => {
37 | this.setState({value})
38 | }
39 |
40 | render() {
41 | const {filename} = this.props
42 | return (
43 |
44 | this.handleOpenOrClose()}>
45 |
46 |
47 |
48 |
49 | {
51 | this.radioGroupRef = ref
52 | }}
53 | value={this.state.value}
54 | onChange={this.handleSelect}
55 | >
56 | {this.props.session_user &&
57 | this.props.session_user.map(user => (
58 | (user !== this.props.self) &&
59 | }
63 | label={user}>
64 |
65 | ))}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | )
75 | }
76 | }
--------------------------------------------------------------------------------
/src/Components/Locations/PopupFunction/ViewClassStudentsInfo.js:
--------------------------------------------------------------------------------
1 | import Popover from '../../PopoverNarrow'
2 | import React, { Fragment } from 'react';
3 | import { Grid, DialogContent, Button, withStyles, Typography} from '@material-ui/core'
4 | import {connection as conn} from '../../../interface/connection'
5 |
6 | const styles = theme => ({
7 | textField: {
8 | marginLeft: theme.spacing.unit,
9 | marginRight: theme.spacing.unit,
10 | width: 200,
11 | },
12 | item: {
13 | fontSize: theme.typography.pxToRem(15),
14 | color: theme.palette.text.secondary,
15 | },
16 | item_green: {
17 | fontSize: theme.typography.pxToRem(15),
18 | color: 'rgba(99, 214, 70)',
19 | }
20 | })
21 |
22 | class ViewClassStudentsInfo extends React.Component {
23 | state = {
24 | open: false,
25 | subed: null,
26 | joined: null
27 | }
28 |
29 | getStudentsInfo = async (c) => {
30 | const response = await conn.call("get_student_names_of_a_class", c)
31 | if (response.result) {
32 | if (response.result.subed.length <= 1) {
33 | this.props.handleNotification(`No one enrolled ${c.class_name} yet`)
34 | return
35 | }
36 | this.setState({
37 | open: true,
38 | subed: response.result.subed,
39 | joined: response.result.joined})
40 | }
41 | }
42 |
43 | render() {
44 | const {classes} = this.props
45 | return (
46 |
47 |
50 | this.setState({open: false})}>
53 |
54 |
60 | {
61 | this.state.subed &&
62 | this.state.subed.map(sname => (
63 | (sname !== this.props.class.owner) &&
64 |
65 | {(this.state.joined.includes(sname)) ?
66 |
67 | {sname}
68 | :
69 |
70 | {sname}
71 |
72 | }
73 |
74 | ))}
75 |
76 |
77 |
78 |
79 | )
80 | }
81 | }
82 |
83 | export default withStyles(styles, {withTheme: true})(ViewClassStudentsInfo);
84 |
--------------------------------------------------------------------------------
/src/Components/Classroom_Components/ParticipantList.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import { withStyles } from '@material-ui/core'
3 | import UserCardSmall from '../Content_Components/UserCardSmall'
4 | import { Grid, Button } from '@material-ui/core'
5 | import RndContainer from './RndContainer';
6 | import ManageGroup from './GroupingMenu/ManageGroup'
7 | import {SignalingChannel} from '../../interface/connection'
8 |
9 | const styles = theme => ({
10 | grid_item: {
11 | marginBottom: 25,
12 | },
13 | participantList : {
14 | padding: 20,
15 | backgroundColor: "rgba(15,25,30,0.15)",
16 | borderRadius: 25,
17 | width: 137
18 | }
19 | })
20 |
21 | class ParticipantList extends Component {
22 | state = {
23 | open: false,
24 | }
25 |
26 | handleOpenGroupingMenu = async () => {
27 | this.setState({open: true})
28 | // const response = await conn.call("get_student_names_of_a_class", c)
29 | // if (response.result) {
30 | // if (response.result.subed.length <= 1) {
31 | // this.props.handleNotification(`No one enrolled ${c.class_name} yet`)
32 | // return
33 | // }
34 | // this.setState({
35 | // open: true,
36 | // subed: response.result.subed,
37 | // joined: response.result.joined})
38 | // }
39 | }
40 |
41 | render() {
42 | const { classes, ...other } = this.props
43 | return (
44 |
45 | this.setState({open: false})}/>
46 |
47 |
54 | {isTeacher(this.props) &&
55 |
56 |
59 |
60 | }
61 |
62 | Students:
63 |
64 | {this.props.session_user &&
65 | this.props.session_user.map(user => (
66 | (user !== this.props.self) &&
67 | (user !== this.props.joined.owner) &&
68 |
69 |
70 |
71 | ))}
72 |
73 |
74 |
75 | )
76 | }
77 | }
78 |
79 | function isTeacher(props) {
80 | return props.joined.owner === props.self
81 | }
82 |
83 | export default withStyles(styles)(ParticipantList)
--------------------------------------------------------------------------------
/src/Components/Content_Components/UserCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import PropTypes from 'prop-types'
3 | import {withStyles} from '@material-ui/core/styles'
4 | import {Card, CardHeader, Divider} from '@material-ui/core'
5 | import LocalStream from '../Classroom_Components/LocalStream'
6 | import RemoteStream from '../Classroom_Components/RemoteStream'
7 | // import UserCardMenu from '../Classroom_Components/UserCardMenu'
8 | import RndContainer from '../Classroom_Components/RndContainer'
9 | import IconButton from '@material-ui/core/IconButton';
10 | import { PinDrop } from '@material-ui/icons';
11 |
12 |
13 | const styles = theme => ({
14 | card: {
15 | width: '100%',
16 | height: '100%'
17 | },
18 | avatar: {
19 | backgroundColor: "#769da8"
20 | },
21 | normalTitle: {color: "#484747", fontSize: 22},
22 | teacherTitle: {color: "#ff4500e6", fontSize: 22}
23 | })
24 |
25 | class UserCard extends React.Component {
26 | constructor(props) {
27 | super(props);
28 | this.state = {
29 | drawRight: 'Read Only',
30 | camOpen: true
31 | }
32 | }
33 |
34 | disableWebcam() {
35 | this.setState({camOpen: false})
36 | }
37 |
38 | render() {
39 | const {user} = this.props;
40 | const {classes, ...other} = this.props;
41 | return (
42 |
43 |
44 | {user} }
47 | style={{
48 | height: 18,
49 | backgroundColor: "#e9e7e74d",
50 | paddingTop: 7,
51 | overflow: "hidden",
52 | whiteSpace: "nowrap",
53 | textOverflow: "ellipsis"
54 | }}
55 | classes={{title: isTeacher(this.props)? classes.teacherTitle : classes.normalTitle}}
56 | // avatar={
57 | //