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);
--------------------------------------------------------------------------------
/src/Components/Locations/PopupFunction/ChangeUserInfo.js:
--------------------------------------------------------------------------------
1 | import Popover from '../../Popover'
2 | import React, { Fragment } from 'react';
3 | import {MenuItem, Grid, DialogContent, TextField, DialogContentText, Button, DialogActions, withStyles} from '@material-ui/core'
4 |
5 | const styles = theme => ({
6 | textField: {
7 | marginLeft: theme.spacing.unit,
8 | marginRight: theme.spacing.unit,
9 | width: 200,
10 | }
11 | })
12 |
13 | class ChangeUserInfo extends React.Component {
14 | state = {
15 | open: false,
16 | name: this.props.self,
17 | age: '',
18 | course: '',
19 | loginName: '',
20 | loginPassword: '',
21 | showPassword: ''
22 | }
23 |
24 | handleChangeUserInfo = () => {
25 | this.setState({open: true})
26 | }
27 |
28 | handleSave = () => {
29 | this.setState({open: false})
30 | }
31 |
32 | handleChangeName = name => event => {
33 | this.setState({ [name]: event.target.value });
34 | };
35 |
36 | handleChangeAge = age => event => {
37 | this.setState({ [age]: event.target.value });
38 | };
39 |
40 | handleChangeCourse = course => event => {
41 | this.setState({ [course]: event.target.value });
42 | };
43 |
44 | render() {
45 | const { classes } = this.props
46 | return (
47 |
48 |
49 | Change UserInfo
50 |
51 |
52 |
53 |
54 |
60 |
61 |
71 |
72 |
73 |
83 |
84 |
85 |
95 |
96 |
97 |
98 |
99 |
100 | Save
101 | Cancel
102 |
103 |
104 |
105 | );
106 | }
107 | }
108 |
109 | export default withStyles(styles, {withTheme: true})(ChangeUserInfo);
110 |
--------------------------------------------------------------------------------
/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 | this.handle()}>Create Class
54 |
55 |
56 |
57 |
58 | {/*{children}*/}
59 |
60 |
69 |
70 |
71 | this.handleSubmit()}>Create
72 |
73 |
74 |
75 | )
76 | }
77 | }
--------------------------------------------------------------------------------
/src/Components/Locations/PopupFunction/EnrollClass.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 | t_name: "",
11 | class_name: ""
12 | }
13 |
14 | handle() {
15 | this.setState({open: !this.state.open});
16 | }
17 |
18 | handleT(e) {
19 | this.setState({t_name: e.target.value});
20 | }
21 |
22 | handleClassName(e) {
23 | this.setState({class_name: e.target.value});
24 | }
25 |
26 | keyPress(e) {
27 | if (e.keyCode === 13) {
28 | e.preventDefault();
29 | this.handleSubmit()
30 | }
31 | }
32 |
33 | handleSubmit() {
34 | conn.call("enroll_class", {owner: this.state.t_name, class_name: this.state.class_name})
35 | .then(async (response) => {
36 | if (response.type === "ok") {
37 | // Promise.all(conn.call("get_enrolled_class"), conn.call("get_started_class"))
38 | // .then(([enrolled, started]) => {
39 | // store.dispatch({
40 | // type: "get_enrolled_class",
41 | // result: enrolled.result
42 | // })
43 | // store.dispatch({
44 | // type: "get_started_class",
45 | // result: started.result
46 | // })
47 | // })
48 | const enrolled = await conn.call("get_enrolled_class")
49 | const started = await conn.call("get_started_class")
50 | store.dispatch({
51 | type: "get_enrolled_class",
52 | result: enrolled.result
53 | })
54 | store.dispatch({
55 | type: "get_started_class",
56 | result: started.result
57 | })
58 | this.props.handleNotification(`Enrolled ${this.state.t_name}'s class: ${this.state.class_name}`)
59 | }
60 | if (response.type === "reject") this.props.handleNotification("Enrolled class FAILED")
61 | })
62 | this.setState({open: false})
63 | }
64 |
65 | render() {
66 | return (
67 |
68 | this.handle()}>Enroll Class
69 |
70 |
71 |
72 |
73 | {/*{children}*/}
74 |
75 |
84 |
92 |
93 |
94 | this.handleSubmit()}>Enroll
95 |
96 |
97 |
98 | )
99 | }
100 | }
--------------------------------------------------------------------------------
/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 | this.getStudentsInfo(this.props.class)}>
48 | Students
49 |
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/Locations/Upload.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { compose } from 'redux'
3 | import { NotificationBar } from '../../Components'
4 | import { TextField, Grid, withStyles } from "@material-ui/core"
5 | import IconButton from '@material-ui/core/IconButton';
6 | import InputAdornment from '@material-ui/core/InputAdornment';
7 | import Visibility from '@material-ui/icons/Visibility';
8 | import VisibilityOff from '@material-ui/icons/VisibilityOff';
9 | import { withCookies } from 'react-cookie'
10 | import '../../css/App.css'
11 | import Dropzone from 'react-dropzone'
12 | import classNames from 'classnames'
13 | import Link from 'react-router-dom/Link'
14 | import {uploadURL} from '../../interface/connection'
15 |
16 | // import axios from 'axios'
17 |
18 | const styles = theme => ({
19 | dropzone: {
20 | position: 'absolute',
21 | top: '65%',
22 | left: '50%',
23 | transform: 'translateX(-50%) translateY(-50%)'
24 | },
25 | form: {
26 | position: 'absolute',
27 | top: '40%',
28 | left: '50%',
29 | transform: 'translateX(-50%) translateY(-50%)'
30 | }
31 | })
32 |
33 | class Upload extends React.Component {
34 | state = {
35 | showNotification: false,
36 | notificationMessage: "",
37 | connected: false,
38 | showPassword: false,
39 | username: "",
40 | password: ""
41 | }
42 |
43 | componentDidMount() {
44 | const {cookies} = this.props
45 | if (cookies.get("name")) {
46 | this.setState({username: cookies.get("name"), password: cookies.get("password")})
47 | }
48 | }
49 |
50 | notificationQueue = []
51 |
52 | handleNotification = (message) => {
53 | this.notificationQueue.push(message)
54 | if (this.state.showNotification) {
55 | this.setState({ showNotification: false });
56 | } else {
57 | this.processQueue();
58 | }
59 | }
60 |
61 | processQueue = () => {
62 | if (this.notificationQueue.length > 0) {
63 | this.setState({
64 | notificationMessage: this.notificationQueue.shift(),
65 | showNotification: true,
66 | })
67 | }
68 | }
69 |
70 | handleDismissNotification = (event, reason) => {
71 | if (reason === 'clickaway') {
72 | return;
73 | }
74 | this.setState({ showNotification: false });
75 | }
76 |
77 | handleName = e => {
78 | this.setState({ username: e.target.value });
79 | }
80 |
81 | handlePassword = (e) => {
82 | this.setState({password: e.target.value});
83 | }
84 |
85 | keyPress = (e) => {
86 | if (e.keyCode === 13) {
87 | e.preventDefault()
88 | }
89 | }
90 |
91 | handleClickShowPassword = () => {
92 | this.setState(state => ({ showPassword: !state.showPassword }));
93 | }
94 |
95 | handleDrop = (acceptedFiles, rejectedFiles) => {
96 | console.log({ acceptedFiles, rejectedFiles })
97 | if (acceptedFiles.length > 0){
98 | let formdata = new FormData()
99 | formdata.append("data", acceptedFiles[0], acceptedFiles[0].name) //3rd arg refer to filename
100 | formdata.append("timestamp", (new Date()).toISOString())
101 | formdata.append("username", this.state.username)
102 | formdata.append("password", this.state.password)
103 | fetch(uploadURL+'/upload', {
104 | method: "POST",
105 | body: formdata
106 | })
107 | .then(response => response.text())
108 | .then(data => this.handleNotification(data))
109 | .catch(e => {this.handleNotification(`${e}`)})
110 | }
111 | }
112 |
113 | render() {
114 | const { classes } = this.props
115 | return (
116 |
117 |
Back to Classroom
118 |
125 |
126 |
135 |
136 |
137 |
138 |
149 |
153 | {this.state.showPassword ? : }
154 |
155 |
156 | ),
157 | }}
158 | />
159 |
160 |
161 |
162 |
163 | {({ getRootProps, getInputProps, isDragActive }) => {
164 | return (
165 |
169 |
170 | {
171 | isDragActive ?
172 |
Drop files here... (50MB max)
:
173 |
Drop files here, or click to select files to upload (50MB max)
174 | }
175 |
176 | )
177 | }}
178 |
179 |
180 |
186 |
187 | )
188 | }
189 | }
190 |
191 | export default compose(
192 | withCookies,
193 | withStyles(styles, { withTheme: true })
194 | )(Upload);
--------------------------------------------------------------------------------
/src/Components/NestedListInFind.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import ListSubheader from '@material-ui/core/ListSubheader';
5 | import List from '@material-ui/core/List';
6 | import ListItem from '@material-ui/core/ListItem';
7 | import ListItemIcon from '@material-ui/core/ListItemIcon';
8 | import ListItemText from '@material-ui/core/ListItemText';
9 | import Collapse from '@material-ui/core/Collapse';
10 | import InboxIcon from '@material-ui/icons/MoveToInbox';
11 | import DraftsIcon from '@material-ui/icons/Drafts';
12 | import SendIcon from '@material-ui/icons/Send';
13 | import ExpandLess from '@material-ui/icons/ExpandLess';
14 | import ExpandMore from '@material-ui/icons/ExpandMore';
15 | import StarBorder from '@material-ui/icons/StarBorder';
16 |
17 | const styles = theme => ({
18 | root: {
19 | width: '100%',
20 | // maxWidth: 360,
21 | backgroundColor: theme.palette.background.paper,
22 | },
23 | nested: {
24 | paddingLeft: theme.spacing.unit * 4,
25 | },
26 | });
27 |
28 | class NestedList extends React.Component {
29 | state = {
30 | open: true,
31 | }
32 |
33 | handleClick = () => {
34 | this.setState(state => ({ open: !state.open }));
35 | }
36 |
37 | render() {
38 | const { classes, components } = this.props
39 |
40 | return (
41 |
Classroom Components}
44 | className={classes.root}
45 | >
46 | {Object.keys(components).map(
47 | (outer) => (
48 |
49 |
50 |
51 |
52 |
53 |
54 | {this.state.open ? : }
55 |
56 | {Object.keys(components[outer]).map((inner) => (
57 |
58 |
59 |
60 | this.props.bringTop(inner)}>
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | ))}
70 | )
71 | )}
72 |
73 | )
74 | }
75 | }
76 |
77 | function getInnerName(inner, outer) {
78 | if (outer === 'webcam') return inner.substring(0, inner.length - 6)
79 | if (outer === 'whiteboard') return inner.substring(0, inner.length - 10)
80 | if (outer === 'drawer') return inner.substring(0, inner.length - 6)
81 | // if (outer === 'other') return inner.substring(0, inner.length - 4)
82 | return inner
83 | }
84 |
85 | NestedList.propTypes = {
86 | classes: PropTypes.object.isRequired,
87 | };
88 |
89 | export default withStyles(styles)(NestedList);
--------------------------------------------------------------------------------
/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 | //
35 | // UNDO
36 | // ,
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/Popover.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Dialog,
3 | // DialogContent, DialogContentText,
4 | DialogTitle, withStyles,
5 | // Button, DialogActions
6 | } from "@material-ui/core";
7 |
8 | const styles = theme => ({
9 | dialog: {
10 | width: '70%',
11 | height: '90%',
12 | maxWidth: '90%',
13 | // maxHeight: 435
14 | }
15 | });
16 |
17 | class Popover extends React.Component {
18 |
19 | render() {
20 | const {title, children, classes, open} = this.props;
21 | return (
22 |
this.props.onClose? this.props.onClose() : null}
25 | aria-labelledby="form-dialog-title"
26 | classes={{paper: classes.dialog}}
27 | >
28 | {title}
29 | {/*
30 |
31 | {children}
32 |
33 |
34 |
35 | Save
36 | Cancel
37 | */}
38 | {children}
39 |
40 | )
41 | }
42 | }
43 |
44 | export default withStyles(styles)(Popover);
--------------------------------------------------------------------------------
/src/Components/PopoverNarrow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Dialog,
3 | // DialogContent, DialogContentText,
4 | DialogTitle, withStyles,
5 | // Button, DialogActions
6 | } from "@material-ui/core";
7 |
8 | const styles = theme => ({
9 | dialog: {
10 | width: '25%',
11 | height: '90%',
12 | maxWidth: '90%',
13 | // maxHeight: 435
14 | }
15 | });
16 |
17 | class PopoverNarrow extends React.Component {
18 |
19 | render() {
20 | const {title, children, classes, open} = this.props;
21 | return (
22 |
this.props.onClose? this.props.onClose() : null}
25 | aria-labelledby="form-dialog-title"
26 | classes={{paper: classes.dialog}}
27 | >
28 | {title}
29 | {/*
30 |
31 | {children}
32 |
33 |
34 |
35 | Save
36 | Cancel
37 | */}
38 | {children}
39 |
40 | )
41 | }
42 | }
43 |
44 | export default withStyles(styles)(PopoverNarrow);
--------------------------------------------------------------------------------
/src/Components/PopoverWithBtn.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Dialog, DialogTitle, withStyles} from "@material-ui/core";
3 |
4 | const styles = theme => ({
5 | dialog: {
6 | // width: '50%',
7 | // height: '80%',
8 | maxHeight: '30%',
9 | maxWidth: '30%',
10 | // maxHeight: 435
11 | }
12 | });
13 |
14 | class PopoverWithBtn extends React.Component {
15 |
16 | render() {
17 | const {title, children, classes, open} = this.props;
18 | return (
19 |
this.props.onClose()}
22 | aria-labelledby="form-dialog-title"
23 | classes={{paper: classes.dialog}}
24 | >
25 | {title}
26 | {children}
27 | {/**/}
28 | {/**/}
29 | {/*{children}*/}
30 | {/* */}
31 | {/* */}
32 | {/**/}
33 | {/*Create */}
34 | {/* */}
35 |
36 | )
37 | }
38 | }
39 |
40 | export default withStyles(styles)(PopoverWithBtn);
--------------------------------------------------------------------------------
/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 | this.handle()} size="small">Register
46 |
47 |
48 |
49 |
50 | {/*{children}*/}
51 |
52 |
61 |
69 |
70 |
71 | this.handleSubmit()}>Register
72 |
73 |
74 |
75 | )
76 | }
77 | }
--------------------------------------------------------------------------------
/src/Components/index.js:
--------------------------------------------------------------------------------
1 | import Content from './Locations/Content'
2 | import Classroom from './Locations/Classroom'
3 | import ClassList from './Locations/ClassList'
4 | import Mailbox from './Locations/Mailbox'
5 | import Notebooks from './Locations/Notebooks'
6 | import NotificationBar from './NotificationBar'
7 | import ClassNotificationBar from './ClassNotificationBar'
8 |
9 | const drawerWidth = 220;
10 |
11 | export {
12 | Content, drawerWidth, Classroom, ClassList, Mailbox, Notebooks, NotificationBar, ClassNotificationBar
13 | }
--------------------------------------------------------------------------------
/src/Login.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import {Button, TextField, Grid, withStyles, Avatar, Fade} from "@material-ui/core"
3 | import RegisterBtn from './Components/RegisterBtn'
4 | import Background from './css/classroom.jpg'
5 | import {withCookies} from 'react-cookie'
6 | import {connection as conn} from './interface/connection'
7 | import {compose} from 'redux'
8 | import {store} from './index'
9 | import Loading from './Components/Loading'
10 | import IconButton from '@material-ui/core/IconButton';
11 | import InputAdornment from '@material-ui/core/InputAdornment';
12 | import Visibility from '@material-ui/icons/Visibility';
13 | import VisibilityOff from '@material-ui/icons/VisibilityOff';
14 |
15 | const styles = theme => ({
16 | loginBg: {
17 | backgroundImage: 'url(' + Background + ')',
18 | filter: 'blur(5px)',
19 | opacity: 0.8,
20 | backgroundSize: 'cover',
21 | overflow: 'hidden',
22 | height: "100vh"
23 | },
24 | bigAvatar: {
25 | margin: 30,
26 | width: 90,
27 | height: 90,
28 | },
29 | loginForm: {
30 | position: 'absolute',
31 | top: '50%',
32 | left: '50%',
33 | transform: 'translateX(-50%) translateY(-50%)'
34 | }
35 | })
36 |
37 | class Login extends React.Component {
38 | // static propTypes = {
39 | // cookies: instanceOf(Cookies).isRequired
40 | // }
41 |
42 | state = {
43 | connected: false,
44 | loginName: "dev",
45 | loginPassword: "dev",
46 | showPassword: false,
47 | fade: false,
48 | refreshIntervalId: null
49 | }
50 |
51 | componentDidMount() {
52 | const refreshIntervalId = setInterval(() => this.setState({fade: true}), 500)
53 | this.setState({refreshIntervalId})
54 | conn.addListener("socketopen", this.handleSocketOpen)
55 | }
56 |
57 | componentWillUnmount() {
58 | clearInterval(this.state.refreshIntervalId)
59 | }
60 |
61 | handleSocketOpen = () => {
62 | conn.removeListener("socketopen", this.handleSocketOpen)
63 | const {cookies} = this.props
64 | this.setState({connected: true})
65 | if (cookies.get("name")) {
66 | this.setState({loginName: cookies.get("name"), loginPassword: cookies.get("password")})
67 | this.handleLogin()
68 | if (cookies.get("location")) {
69 | // TODO parseInt in production
70 | store.dispatch({type: "changeLocation", target: parseFloat(cookies.get("location"), 10)})
71 | }
72 | } else {
73 | this.setState({connected: true})
74 | }
75 | }
76 |
77 | handleLogin = async () => {
78 | const result = await conn.call("login", {username: this.state.loginName, password: this.state.loginPassword})
79 | if (result["type"] === "ok") {
80 | const {cookies} = this.props
81 | this.setState({connected: true})
82 | cookies.set("name", this.state.loginName) // option: {path: "/"}
83 | cookies.set("password", this.state.loginPassword)
84 | this.setState({loginPassword: ""})
85 | store.dispatch({type:"login", loginName: this.state.loginName})
86 | //TODO listen the following events
87 | const created = await conn.call("get_created_class")
88 | const enrolled = await conn.call("get_enrolled_class")
89 | const started = await conn.call("get_started_class")
90 | store.dispatch({
91 | type:"get_created_class",
92 | result: created.result
93 | })
94 | store.dispatch({
95 | type: "get_enrolled_class",
96 | result: enrolled.result
97 | })
98 | store.dispatch({
99 | type: "get_started_class",
100 | result: started.result
101 | })
102 | this.props.handleNotification(`Welcome ${this.state.loginName}`)
103 | } else {
104 | if (result["type"] === "reject") {
105 | if (result["reason"] === "invalid_params") this.props.handleNotification("Invalid username or password")
106 | //TODO prompt and force login
107 | if (result["reason"] === "already_logged_in") this.props.handleNotification("Already logged in")
108 | }
109 | }
110 | }
111 |
112 | handleName = (e) => {
113 | this.setState({loginName: e.target.value});
114 | }
115 |
116 | handlePassword = (e) => {
117 | this.setState({loginPassword: e.target.value});
118 | }
119 |
120 | keyPress = (e) => {
121 | if (e.keyCode === 13) {
122 | e.preventDefault();
123 | this.handleLogin()
124 | }
125 | }
126 |
127 | handleClickShowPassword = () => {
128 | this.setState(state => ({ showPassword: !state.showPassword }));
129 | }
130 |
131 | render() {
132 | const {classes} = this.props
133 | return (
134 |
135 | {conn.connected() &&
136 |
}
137 | {conn.connected() &&
138 |
139 |
146 |
147 | A
148 |
149 |
150 | Name
151 |
152 |
153 |
162 |
163 |
164 |
165 |
176 |
180 | {this.state.showPassword ? : }
181 |
182 |
183 | ),
184 | }}
185 | />
186 |
187 |
188 |
189 |
196 | Login
197 |
198 |
199 |
200 |
201 |
202 |
203 | }
204 | {!conn.connected() &&
205 |
206 | }
207 |
208 | )
209 | }
210 | }
211 |
212 | export default compose(
213 | withCookies,
214 | withStyles(styles, {withTheme: true}),
215 | )(Login)
--------------------------------------------------------------------------------
/src/css/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | /*font: 28px "Century Gothic", Futura, sans-serif;*/
3 | /*margin: 40px;*/
4 | background-color: #fafafa;
5 | }
6 |
7 | /*.container {*/
8 | /*height: 1000px;*/
9 | /*background-color: #ececec;*/
10 | /*margin: auto;*/
11 | /*width: 60%;*/
12 | /*}*/
13 |
14 | /*.classroom {*/
15 | /*display: flex;*/
16 | /*flex-direction: column;*/
17 | /*align-items: left;*/
18 | /*font: 28px "Century Gothic", Futura, sans-serif;*/
19 | /*padding-left: 40px;*/
20 | /*padding-top: 40px;*/
21 | /*}*/
22 |
23 | /*.control_panel {*/
24 | /*margin-left: 40px;*/
25 | /*}*/
--------------------------------------------------------------------------------
/src/css/ask_camera_permission.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herbert1228/online-classroom-react.js-client/dc2fc3bc23ea27574f1b026d814721aaa0306189/src/css/ask_camera_permission.jpeg
--------------------------------------------------------------------------------
/src/css/ask_camera_permission.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herbert1228/online-classroom-react.js-client/dc2fc3bc23ea27574f1b026d814721aaa0306189/src/css/ask_camera_permission.jpg
--------------------------------------------------------------------------------
/src/css/ask_camera_permission.jpg.sb-e2fe0202-Ek4Ixv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herbert1228/online-classroom-react.js-client/dc2fc3bc23ea27574f1b026d814721aaa0306189/src/css/ask_camera_permission.jpg.sb-e2fe0202-Ek4Ixv
--------------------------------------------------------------------------------
/src/css/ask_camera_permission.jpg.sb-e2fe0202-rASvL6:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herbert1228/online-classroom-react.js-client/dc2fc3bc23ea27574f1b026d814721aaa0306189/src/css/ask_camera_permission.jpg.sb-e2fe0202-rASvL6
--------------------------------------------------------------------------------
/src/css/classroom.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herbert1228/online-classroom-react.js-client/dc2fc3bc23ea27574f1b026d814721aaa0306189/src/css/classroom.jpg
--------------------------------------------------------------------------------
/src/css/classroom.jpg.bk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herbert1228/online-classroom-react.js-client/dc2fc3bc23ea27574f1b026d814721aaa0306189/src/css/classroom.jpg.bk
--------------------------------------------------------------------------------
/src/css/classroom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herbert1228/online-classroom-react.js-client/dc2fc3bc23ea27574f1b026d814721aaa0306189/src/css/classroom.png
--------------------------------------------------------------------------------
/src/css/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
--------------------------------------------------------------------------------
/src/css/whiteboard-no-cut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herbert1228/online-classroom-react.js-client/dc2fc3bc23ea27574f1b026d814721aaa0306189/src/css/whiteboard-no-cut.png
--------------------------------------------------------------------------------
/src/css/whiteboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herbert1228/online-classroom-react.js-client/dc2fc3bc23ea27574f1b026d814721aaa0306189/src/css/whiteboard.png
--------------------------------------------------------------------------------
/src/css/yoda.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herbert1228/online-classroom-react.js-client/dc2fc3bc23ea27574f1b026d814721aaa0306189/src/css/yoda.jpg
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from './App'
3 | import Upload from './Components/Locations/Upload'
4 | // import registerServiceWorker from './registerServiceWorker';
5 | import {createMuiTheme, MuiThemeProvider} from '@material-ui/core/styles'
6 | import {amber, blueGrey} from '@material-ui/core/colors'
7 | import {BrowserRouter as Router, Route} from "react-router-dom"
8 | import { Provider } from 'react-redux'
9 | import { createStore } from 'redux'
10 | import ReactDOM from 'react-dom'
11 | import { connection as conn } from './interface/connection'
12 |
13 | let themeType = 'light';
14 |
15 | const theme = createMuiTheme({
16 | palette: {
17 | primary: {main: blueGrey[800]},
18 | secondary: amber,
19 | type: themeType
20 | }
21 | })
22 |
23 | const initialState = {
24 | createdClass: [],
25 | enrolledClass: [], // class_name, owner, online and offline students number of every class
26 | startedClass: [],
27 | session_user: [], // user currently in the same class
28 | peerConn: [], // session_user who is ready for peer connection (as webcam turned on)
29 | location: 0,
30 | self: null,
31 | joined: null,
32 | lastJoin: null,
33 | drawerOpen: true,
34 | groupCards: [],
35 | group: {group: null, members: []},
36 | webcamPermission: {video: true, audio: true}
37 | }
38 |
39 | function reducer(state = initialState, action) {
40 | // console.log(action)
41 | switch(action.type) {
42 | case "get_created_class":
43 | return {...state, createdClass: action.result}
44 | case "get_enrolled_class":
45 | return {...state, enrolledClass: action.result}
46 | case "get_started_class":
47 | return {...state, startedClass: action.result}
48 | case "get_session_user":
49 | return {...state, session_user: action.result}
50 | case "get_exist_peer_conn":
51 | return {...state, peerConn: action.result}
52 | case "changeLocation":
53 | return {...state, location: action.target}
54 | case "logout":
55 | return {...state, self: null, joined: null}
56 | case "login":
57 | return {...state, self: action.loginName}
58 | case "joinClass":
59 | const joined = {owner: action.owner, class_name: action.class_name}
60 | return {...state, joined, lastJoin: joined}
61 | case "leaveClass":
62 | return {...state, joined: null, session_user: []}
63 | case "drawerOpen":
64 | return {...state, drawerOpen: action.drawerOpen}
65 | case "updateGroupCards":
66 | return {...state, groupCards: action.groupCards}
67 | case "updateGroup":
68 | return {...state, group: action.group}
69 | case "updateWebcamPermission":
70 | return {...state, webcamPermission: action.webcamPermission}
71 | default:
72 | return state
73 | }
74 | }
75 |
76 | export const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
77 |
78 | class AppContainer extends React.Component {
79 | componentDidMount() {
80 | conn.connect()
81 | conn.addListener("socketclose", this.handleSocketClose)
82 | conn.addListener("get_started_class", (e) => this.dispatch("get_started_class", e.result))
83 | conn.addListener("broadcast_message", (e) => console.log(e.result))
84 | conn.addListener("get_session_user", (e) => this.dispatch("get_session_user", e.result))
85 | conn.addListener("get_exist_peer_conn", (e) => this.dispatch("get_exist_peer_conn", e.result))
86 | }
87 |
88 | componentWillUnmount() {
89 | conn.removeListener("socketclose", this.handleSocketClose)
90 | conn.removeListener("get_started_class", (e) => this.dispatch("get_started_class", e.result))
91 | conn.removeListener("broadcast_message", (e) => console.log(e.result))
92 | conn.removeListener("get_session_user", (e) => this.dispatch("get_session_user", e.result))
93 | conn.removeListener("get_exist_peer_conn", (e) => this.dispatch("get_exist_peer_conn", e.result))
94 | }
95 |
96 | dispatch = (type, result) => {
97 | store.dispatch({type, result})
98 | }
99 |
100 | handleSocketClose = () => {
101 | //TODO relogin here
102 | console.log("attempting to reconnect...")
103 | setTimeout(() => {
104 | store.dispatch({type: "logout"}) //TODO error: cannot set self to null
105 | conn.connect()
106 | }, 2000)
107 | this.setState({joined: null})
108 | }
109 |
110 | render() {
111 | return (
112 |
113 | )
114 | }
115 | }
116 |
117 | function Index() {
118 | return (
119 |
120 |
121 |
122 |
123 |
}/>
124 | }/>
125 |
126 |
127 |
128 |
129 | )
130 | }
131 |
132 | ReactDOM.render(
, document.getElementById('root'));
133 |
134 | // registerServiceWorker();
135 |
--------------------------------------------------------------------------------
/src/interface/util.js:
--------------------------------------------------------------------------------
1 | const notification = {
2 |
3 | }
4 |
5 | export {
6 |
7 | }
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------