Merge branch 'assessment' into tutorial
This commit is contained in:
commit
e322795f25
24
src/actions/tutorialActions.js
Normal file
24
src/actions/tutorialActions.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE } from './types';
|
||||||
|
|
||||||
|
import { tutorials } from '../components/Tutorial/tutorials';
|
||||||
|
|
||||||
|
export const tutorialChange = () => (dispatch) => {
|
||||||
|
dispatch({
|
||||||
|
type: TUTORIAL_CHANGE
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tutorialCheck = (id, status) => (dispatch, getState) => {
|
||||||
|
var tutorialsStatus = getState().tutorial.status ?
|
||||||
|
getState().tutorial.status
|
||||||
|
: new Array(tutorials.length).fill({});
|
||||||
|
tutorialsStatus[id].status = status;
|
||||||
|
console.log(tutorials);
|
||||||
|
dispatch({
|
||||||
|
type: status === 'success' ? TUTORIAL_SUCCESS : TUTORIAL_ERROR,
|
||||||
|
payload: tutorialsStatus
|
||||||
|
});
|
||||||
|
dispatch(tutorialChange());
|
||||||
|
// update locale storage - sync with redux store
|
||||||
|
window.localStorage.setItem('tutorial', JSON.stringify(tutorialsStatus));
|
||||||
|
};
|
@ -5,3 +5,8 @@ export const MOVE_BLOCK = 'MOVE_BLOCK';
|
|||||||
export const CHANGE_BLOCK = 'CHANGE_BLOCK';
|
export const CHANGE_BLOCK = 'CHANGE_BLOCK';
|
||||||
export const DELETE_BLOCK = 'DELETE_BLOCK';
|
export const DELETE_BLOCK = 'DELETE_BLOCK';
|
||||||
export const CLEAR_STATS = 'CLEAR_STATS';
|
export const CLEAR_STATS = 'CLEAR_STATS';
|
||||||
|
|
||||||
|
|
||||||
|
export const TUTORIAL_SUCCESS = 'TUTORIAL_SUCCESS';
|
||||||
|
export const TUTORIAL_ERROR = 'TUTORIAL_ERROR';
|
||||||
|
export const TUTORIAL_CHANGE = 'TUTORIAL_CHANGE';
|
||||||
|
@ -62,7 +62,7 @@ class Compile extends Component {
|
|||||||
return (
|
return (
|
||||||
<div style={{display: 'inline'}}>
|
<div style={{display: 'inline'}}>
|
||||||
<Button style={{ float: 'right', color: 'white' }} variant="contained" color="primary" onClick={() => this.compile()}>
|
<Button style={{ float: 'right', color: 'white' }} variant="contained" color="primary" onClick={() => this.compile()}>
|
||||||
Compile
|
Kompilieren
|
||||||
</Button>
|
</Button>
|
||||||
<Backdrop className={this.props.classes.backdrop} open={this.state.progress}>
|
<Backdrop className={this.props.classes.backdrop} open={this.state.progress}>
|
||||||
<CircularProgress color="inherit" />
|
<CircularProgress color="inherit" />
|
||||||
@ -70,7 +70,7 @@ class Compile extends Component {
|
|||||||
<Dialog onClose={this.toggleDialog} open={this.state.open}>
|
<Dialog onClose={this.toggleDialog} open={this.state.open}>
|
||||||
<DialogTitle>Fehler</DialogTitle>
|
<DialogTitle>Fehler</DialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
Etwas ist beim Compilieren schief gelaufen. Versuche es nochmal.
|
Etwas ist beim Kompilieren schief gelaufen. Versuche es nochmal.
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={this.toggleDialog} color="primary">
|
<Button onClick={this.toggleDialog} color="primary">
|
||||||
|
106
src/components/Tutorial/SolutionCheck.js
Normal file
106
src/components/Tutorial/SolutionCheck.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { tutorialCheck } from '../../actions/tutorialActions';
|
||||||
|
|
||||||
|
import * as Blockly from 'blockly/core';
|
||||||
|
|
||||||
|
import Compile from '../Compile';
|
||||||
|
|
||||||
|
import { tutorials } from './tutorials';
|
||||||
|
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
|
import Tooltip from '@material-ui/core/Tooltip';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||||
|
import DialogContent from '@material-ui/core/DialogContent';
|
||||||
|
import DialogActions from '@material-ui/core/DialogActions';
|
||||||
|
import Dialog from '@material-ui/core/Dialog';
|
||||||
|
import { faPlay } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
const styles = (theme) => ({
|
||||||
|
compile: {
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
color: theme.palette.primary.contrastText,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
color: theme.palette.primary.contrastText,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class SolutionCheck extends Component {
|
||||||
|
|
||||||
|
state={
|
||||||
|
open: false,
|
||||||
|
msg: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDialog = () => {
|
||||||
|
if(this.state.open){
|
||||||
|
this.setState({ open: false, msg: '' });
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
this.setState({ open: !this.state });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check = () => {
|
||||||
|
const workspace = Blockly.getMainWorkspace();
|
||||||
|
var msg = tutorials[this.props.tutorial].test(workspace);
|
||||||
|
this.props.tutorialCheck(this.props.tutorial, msg.type);
|
||||||
|
this.setState({ msg, open: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
tutorials[this.props.tutorial].test ?
|
||||||
|
<div>
|
||||||
|
<Tooltip title='Lösung kontrollieren'>
|
||||||
|
<IconButton
|
||||||
|
className={this.props.classes.compile}
|
||||||
|
style={{width: '40px', height: '40px', position: 'absolute', top: 8, right: 8, zIndex: 21 }}
|
||||||
|
onClick={() => this.check()}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faPlay} size="xs"/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Dialog fullWidth maxWidth={'sm'} onClose={this.toggleDialog} open={this.state.open} style={{zIndex: 9999999}}>
|
||||||
|
<DialogTitle>{this.state.msg.type === 'error' ? 'Fehler' : 'Erfolg'}</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
{this.state.msg.text}
|
||||||
|
{this.state.msg.type === 'success' ?
|
||||||
|
<div style={{marginTop: '20px', display: 'flex'}}>
|
||||||
|
<Compile />
|
||||||
|
<Button
|
||||||
|
style={{marginLeft: '10px'}}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {this.toggleDialog(); this.props.history.push(`/tutorial/${this.props.tutorial+2}`)}}
|
||||||
|
>
|
||||||
|
nächstes Tutorial
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={this.toggleDialog} color="primary">
|
||||||
|
Schließen
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SolutionCheck.propTypes = {
|
||||||
|
tutorialCheck: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(null, { tutorialCheck })(withRouter(withStyles(styles, {withTheme: true})(SolutionCheck)));
|
@ -1,8 +1,12 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import tutorials from './tutorials.json';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
import { tutorials } from './tutorials';
|
||||||
|
|
||||||
import { fade } from '@material-ui/core/styles/colorManipulator';
|
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
@ -11,9 +15,11 @@ import Stepper from '@material-ui/core/Stepper';
|
|||||||
import Step from '@material-ui/core/Step';
|
import Step from '@material-ui/core/Step';
|
||||||
import StepLabel from '@material-ui/core/StepLabel';
|
import StepLabel from '@material-ui/core/StepLabel';
|
||||||
|
|
||||||
|
import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
const styles = (theme) => ({
|
const styles = (theme) => ({
|
||||||
stepper: {
|
stepper: {
|
||||||
backgroundColor: fade(theme.palette.primary.main, 0.6),
|
|
||||||
width: 'calc(100% - 40px)',
|
width: 'calc(100% - 40px)',
|
||||||
borderRadius: '25px',
|
borderRadius: '25px',
|
||||||
padding: '0 20px',
|
padding: '0 20px',
|
||||||
@ -21,8 +27,23 @@ const styles = (theme) => ({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between'
|
justifyContent: 'space-between'
|
||||||
},
|
},
|
||||||
|
stepperSuccess: {
|
||||||
|
backgroundColor: fade(theme.palette.primary.main, 0.6),
|
||||||
|
},
|
||||||
|
stepperError: {
|
||||||
|
backgroundColor: fade(theme.palette.error.dark, 0.6),
|
||||||
|
},
|
||||||
|
stepperOther: {
|
||||||
|
backgroundColor: fade(theme.palette.secondary.main, 0.6),
|
||||||
|
},
|
||||||
color: {
|
color: {
|
||||||
backgroundColor: 'transparent '
|
backgroundColor: 'transparent '
|
||||||
|
},
|
||||||
|
iconDivSuccess: {
|
||||||
|
color: theme.palette.primary.main
|
||||||
|
},
|
||||||
|
iconDivError: {
|
||||||
|
color: theme.palette.error.dark
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -40,8 +61,10 @@ class StepperHorizontal extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
var tutorialId = this.state.tutorialId;
|
var tutorialId = this.state.tutorialId;
|
||||||
|
var tutorialStatus = this.props.status[tutorialId-1].status === 'success' ? 'Success' :
|
||||||
|
this.props.status[tutorialId-1].status === 'error' ? 'Error' : 'Other';
|
||||||
return (
|
return (
|
||||||
<div className={this.props.classes.stepper}>
|
<div className={clsx(this.props.classes.stepper, this.props.classes['stepper'+tutorialStatus])}>
|
||||||
<Button
|
<Button
|
||||||
disabled={tutorialId-1 === 0}
|
disabled={tutorialId-1 === 0}
|
||||||
onClick={() => {this.props.history.push(`/tutorial/${tutorialId-1}`)}}
|
onClick={() => {this.props.history.push(`/tutorial/${tutorialId-1}`)}}
|
||||||
@ -51,7 +74,7 @@ class StepperHorizontal extends Component {
|
|||||||
<Stepper activeStep={tutorialId} orientation="horizontal"
|
<Stepper activeStep={tutorialId} orientation="horizontal"
|
||||||
style={{padding: 0}} classes={{root: this.props.classes.color}}>
|
style={{padding: 0}} classes={{root: this.props.classes.color}}>
|
||||||
<Step expanded completed={false}>
|
<Step expanded completed={false}>
|
||||||
<StepLabel icon={``}>
|
<StepLabel icon={tutorialStatus !== 'Other' ? <div className={clsx(tutorialStatus === 'Error' ? this.props.classes.iconDivError: this.props.classes.iconDivSuccess)}><FontAwesomeIcon className={this.props.classes.icon} icon={tutorialStatus === 'Success' ? faCheck : faTimes}/></div> : ''}>
|
||||||
<h1 style={{margin: 0}}>{tutorials[tutorialId-1].title}</h1>
|
<h1 style={{margin: 0}}>{tutorials[tutorialId-1].title}</h1>
|
||||||
</StepLabel>
|
</StepLabel>
|
||||||
</Step>
|
</Step>
|
||||||
@ -67,4 +90,14 @@ class StepperHorizontal extends Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(withStyles(styles, {withTheme: true})(StepperHorizontal));
|
StepperHorizontal.propTypes = {
|
||||||
|
status: PropTypes.array.isRequired,
|
||||||
|
change: PropTypes.number.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
change: state.tutorial.change,
|
||||||
|
status: state.tutorial.status
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, null)(withRouter(withStyles(styles, {withTheme: true})(StepperHorizontal)));
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { withRouter, Link } from 'react-router-dom';
|
import { withRouter, Link } from 'react-router-dom';
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
import tutorials from './tutorials.json';
|
import { tutorials } from './tutorials';
|
||||||
|
|
||||||
import { fade } from '@material-ui/core/styles/colorManipulator';
|
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
@ -20,33 +22,44 @@ const styles = (theme) => ({
|
|||||||
padding: 0,
|
padding: 0,
|
||||||
width: '30px',
|
width: '30px',
|
||||||
},
|
},
|
||||||
stepIconSmall: {
|
stepIcon: {
|
||||||
border: `2px solid ${theme.palette.primary.main}`,
|
borderStyle: `solid`,
|
||||||
|
borderWith: '2px',
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
width: '12px',
|
width: '12px',
|
||||||
height: '12px',
|
height: '12px',
|
||||||
margin: '0 auto'
|
margin: '0 auto'
|
||||||
},
|
},
|
||||||
stepIconMedium: {
|
stepIconMedium: {
|
||||||
border: `2px solid ${theme.palette.primary.main}`,
|
|
||||||
borderRadius: '50%',
|
|
||||||
width: '18px',
|
width: '18px',
|
||||||
height: '18px',
|
height: '18px',
|
||||||
margin: '0 auto'
|
|
||||||
},
|
},
|
||||||
stepIconLarge: {
|
stepIconLarge: {
|
||||||
border: `2px solid ${theme.palette.primary.main}`,
|
|
||||||
borderRadius: '50%',
|
|
||||||
width: '24px',
|
width: '24px',
|
||||||
height: '24px'
|
height: '24px'
|
||||||
},
|
},
|
||||||
stepIconTransparent: {
|
stepIconTransparent: {
|
||||||
border: `2px solid transparent`,
|
borderColor: `transparent`,
|
||||||
cursor: 'default'
|
cursor: 'default'
|
||||||
},
|
},
|
||||||
stepIconActive: {
|
stepIconSuccess: {
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
stepIconError: {
|
||||||
|
borderColor: theme.palette.error.dark,
|
||||||
|
},
|
||||||
|
stepIconOther: {
|
||||||
|
borderColor: theme.palette.secondary.main,
|
||||||
|
},
|
||||||
|
stepIconActiveSuccess: {
|
||||||
backgroundColor: fade(theme.palette.primary.main, 0.6)
|
backgroundColor: fade(theme.palette.primary.main, 0.6)
|
||||||
},
|
},
|
||||||
|
stepIconActiveError: {
|
||||||
|
backgroundColor: fade(theme.palette.error.dark, 0.6)
|
||||||
|
},
|
||||||
|
stepIconActiveOther: {
|
||||||
|
backgroundColor: fade(theme.palette.secondary.main, 0.6)
|
||||||
|
},
|
||||||
progress: {
|
progress: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
@ -76,7 +89,7 @@ class StepperVertical extends Component {
|
|||||||
tutorials.slice(Number(this.props.match.params.tutorialId)-3-1, Number(this.props.match.params.tutorialId)+3)
|
tutorials.slice(Number(this.props.match.params.tutorialId)-3-1, Number(this.props.match.params.tutorialId)+3)
|
||||||
: tutorials.slice(Number(this.props.match.params.tutorialId)-2-1,Number(this.props.match.params.tutorialId)+2),
|
: tutorials.slice(Number(this.props.match.params.tutorialId)-2-1,Number(this.props.match.params.tutorialId)+2),
|
||||||
tutorialId: Number(this.props.match.params.tutorialId),
|
tutorialId: Number(this.props.match.params.tutorialId),
|
||||||
verticalTutorialId: Number(this.props.match.params.tutorialId)
|
selectedVerticalTutorialId: Number(this.props.match.params.tutorialId)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(props, state){
|
componentDidUpdate(props, state){
|
||||||
@ -92,13 +105,13 @@ class StepperVertical extends Component {
|
|||||||
tutorials.slice(Number(this.props.match.params.tutorialId)-3-1, Number(this.props.match.params.tutorialId)+3)
|
tutorials.slice(Number(this.props.match.params.tutorialId)-3-1, Number(this.props.match.params.tutorialId)+3)
|
||||||
: tutorials.slice(Number(this.props.match.params.tutorialId)-2-1,Number(this.props.match.params.tutorialId)+2),
|
: tutorials.slice(Number(this.props.match.params.tutorialId)-2-1,Number(this.props.match.params.tutorialId)+2),
|
||||||
tutorialId: Number(this.props.match.params.tutorialId),
|
tutorialId: Number(this.props.match.params.tutorialId),
|
||||||
verticalTutorialId: Number(this.props.match.params.tutorialId)
|
selectedVerticalTutorialId: Number(this.props.match.params.tutorialId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verticalStepper = (step) => {
|
verticalStepper = (step) => {
|
||||||
var newTutorialId = this.state.verticalTutorialId + step;
|
var newTutorialId = this.state.selectedVerticalTutorialId + step;
|
||||||
var tutorialArray = Number(newTutorialId) === 1 ?
|
var tutorialArray = Number(newTutorialId) === 1 ?
|
||||||
tutorials.slice(newTutorialId-1, newTutorialId+4)
|
tutorials.slice(newTutorialId-1, newTutorialId+4)
|
||||||
: newTutorialId === 2 ?
|
: newTutorialId === 2 ?
|
||||||
@ -108,31 +121,31 @@ class StepperVertical extends Component {
|
|||||||
: newTutorialId === tutorials.length-1 ?
|
: newTutorialId === tutorials.length-1 ?
|
||||||
tutorials.slice(newTutorialId-3-1, newTutorialId+3)
|
tutorials.slice(newTutorialId-3-1, newTutorialId+3)
|
||||||
: tutorials.slice(newTutorialId-2-1, newTutorialId+2);
|
: tutorials.slice(newTutorialId-2-1, newTutorialId+2);
|
||||||
this.setState({ tutorialArray: tutorialArray, verticalTutorialId: newTutorialId });
|
this.setState({ tutorialArray: tutorialArray, selectedVerticalTutorialId: newTutorialId });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var tutorialId = this.state.tutorialId;
|
var tutorialId = this.state.tutorialId;
|
||||||
var verticalTutorialId = this.state.verticalTutorialId;
|
var selectedVerticalTutorialId = this.state.selectedVerticalTutorialId;
|
||||||
return (
|
return (
|
||||||
isWidthUp('sm', this.props.width) ?
|
isWidthUp('sm', this.props.width) ?
|
||||||
<div style={{marginRight: '10px'}}>
|
<div style={{marginRight: '10px'}}>
|
||||||
<Button
|
<Button
|
||||||
style={{minWidth: '30px', margin: 'auto', minHeight: '25px', padding: '0', writingMode: 'vertical-rl'}}
|
style={{minWidth: '30px', margin: 'auto', minHeight: '25px', padding: '0', writingMode: 'vertical-rl'}}
|
||||||
disabled={this.state.verticalTutorialId === 1}
|
disabled={this.state.selectedVerticalTutorialId === 1}
|
||||||
onClick={() => {this.verticalStepper(-1)}}
|
onClick={() => {this.verticalStepper(-1)}}
|
||||||
>
|
>
|
||||||
{'<'}
|
{'<'}
|
||||||
</Button>
|
</Button>
|
||||||
<div style={{display: 'flex', height: 'calc(100% - 25px - 25px)', width: 'max-content'}}>
|
<div style={{display: 'flex', height: 'max-content', width: 'max-content'}}>
|
||||||
<div style={{position: 'relative'}}>
|
<div style={{position: 'relative'}}>
|
||||||
<div
|
<div
|
||||||
className={clsx(this.props.classes.progress, this.props.classes.progressForeground)}
|
className={clsx(this.props.classes.progress, this.props.classes.progressForeground)}
|
||||||
style={{ zIndex: 1, borderRadius: `${verticalTutorialId/tutorials.length === 1 ? '2px' : '2px 2px 0 0'}`, height: `${(verticalTutorialId/tutorials.length)*100}%`}}>
|
style={{ zIndex: 1, borderRadius: `${selectedVerticalTutorialId/tutorials.length === 1 ? '2px' : '2px 2px 0 0'}`, height: `${(selectedVerticalTutorialId/tutorials.length)*100}%`}}>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={clsx(this.props.classes.progress, this.props.classes.progressBackground)}
|
className={clsx(this.props.classes.progress, this.props.classes.progressBackground)}
|
||||||
style={{borderRadius: `${verticalTutorialId/tutorials.length === 1 ? '2px' : '2px 2px 0 0'}`}}>
|
style={{borderRadius: `${selectedVerticalTutorialId/tutorials.length === 1 ? '2px' : '2px 2px 0 0'}`}}>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Stepper
|
<Stepper
|
||||||
@ -142,25 +155,28 @@ class StepperVertical extends Component {
|
|||||||
classes={{root: this.props.classes.verticalStepper}}
|
classes={{root: this.props.classes.verticalStepper}}
|
||||||
>
|
>
|
||||||
{this.state.tutorialArray.map((tutorial, i) => {
|
{this.state.tutorialArray.map((tutorial, i) => {
|
||||||
var index = this.state.tutorialArray.indexOf(tutorials[verticalTutorialId-1]);
|
var index = this.state.tutorialArray.indexOf(tutorials[selectedVerticalTutorialId-1]);
|
||||||
|
var verticalTutorialId = i === index ? selectedVerticalTutorialId : selectedVerticalTutorialId - index + i;
|
||||||
|
var tutorialStatus = this.props.status[verticalTutorialId-1].status === 'success' ? 'Success' :
|
||||||
|
this.props.status[verticalTutorialId-1].status === 'error' ? 'Error' : 'Other';
|
||||||
return (
|
return (
|
||||||
<Step key={i}>
|
<Step key={i}>
|
||||||
<Tooltip title={Object.keys(tutorial).length > 0 ? tutorial.title : ''} placement='right' arrow >
|
<Tooltip title={Object.keys(tutorial).length > 0 ? tutorial.title : ''} placement='right' arrow >
|
||||||
<Link to={`/tutorial/${i === index ? verticalTutorialId : verticalTutorialId - index + i}`}>
|
<Link to={`/tutorial/${verticalTutorialId}`}>
|
||||||
<StepLabel
|
<StepLabel
|
||||||
StepIconComponent={'div'}
|
StepIconComponent={'div'}
|
||||||
classes={{
|
classes={{
|
||||||
root: tutorial === tutorials[verticalTutorialId-1] ?
|
root: tutorial === tutorials[selectedVerticalTutorialId-1] ?
|
||||||
tutorial === tutorials[tutorialId-1] ?
|
tutorial === tutorials[tutorialId-1] ?
|
||||||
clsx(this.props.classes.stepIconLarge, this.props.classes.stepIconActive)
|
clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIcon'+tutorialStatus], this.props.classes['stepIconActive'+tutorialStatus])
|
||||||
: this.props.classes.stepIconLarge
|
: clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIcon'+tutorialStatus])
|
||||||
: tutorial === tutorials[verticalTutorialId-2] || tutorial === tutorials[verticalTutorialId] ?
|
: tutorial === tutorials[verticalTutorialId-2] || tutorial === tutorials[selectedVerticalTutorialId] ?
|
||||||
tutorial === tutorials[tutorialId-1] ?
|
tutorial === tutorials[tutorialId-1] ?
|
||||||
clsx(this.props.classes.stepIconMedium, this.props.classes.stepIconActive)
|
clsx(this.props.classes.stepIcon, this.props.classes.stepIconMedium, this.props.classes['stepIcon'+tutorialStatus], this.props.classes['stepIconActive'+tutorialStatus])
|
||||||
: this.props.classes.stepIconMedium
|
: clsx(this.props.classes.stepIcon, this.props.classes.stepIconMedium, this.props.classes['stepIcon'+tutorialStatus])
|
||||||
: tutorial === tutorials[tutorialId-1] ?
|
: tutorial === tutorials[tutorialId-1] ?
|
||||||
clsx(this.props.classes.stepIconSmall, this.props.classes.stepIconActive)
|
clsx(this.props.classes.stepIcon, this.props.classes['stepIcon'+tutorialStatus], this.props.classes['stepIconActive'+tutorialStatus])
|
||||||
: this.props.classes.stepIconSmall
|
: clsx(this.props.classes.stepIcon, this.props.classes['stepIcon'+tutorialStatus])
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
</StepLabel>
|
</StepLabel>
|
||||||
@ -172,7 +188,7 @@ class StepperVertical extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
style={{minWidth: '30px', minHeight: '25px', padding: '0', writingMode: 'vertical-rl'}}
|
style={{minWidth: '30px', minHeight: '25px', padding: '0', writingMode: 'vertical-rl'}}
|
||||||
disabled={this.state.verticalTutorialId === tutorials.length}
|
disabled={this.state.selectedVerticalTutorialId === tutorials.length}
|
||||||
onClick={() => {this.verticalStepper(1)}}
|
onClick={() => {this.verticalStepper(1)}}
|
||||||
>
|
>
|
||||||
{'>'}
|
{'>'}
|
||||||
@ -183,4 +199,15 @@ class StepperVertical extends Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(withStyles(styles, {withTheme: true})(withWidth()(StepperVertical)));
|
|
||||||
|
StepperVertical.propTypes = {
|
||||||
|
status: PropTypes.array.isRequired,
|
||||||
|
change: PropTypes.number.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
change: state.tutorial.change,
|
||||||
|
status: state.tutorial.status
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, null)(withRouter(withStyles(styles, {withTheme: true})(withWidth()(StepperVertical))));
|
||||||
|
@ -4,10 +4,11 @@ import Breadcrumbs from '../Breadcrumbs';
|
|||||||
import StepperHorizontal from './StepperHorizontal';
|
import StepperHorizontal from './StepperHorizontal';
|
||||||
import StepperVertical from './StepperVertical';
|
import StepperVertical from './StepperVertical';
|
||||||
import BlocklyWindow from '../Blockly/BlocklyWindow';
|
import BlocklyWindow from '../Blockly/BlocklyWindow';
|
||||||
|
import SolutionCheck from './SolutionCheck';
|
||||||
import CodeViewer from '../CodeViewer';
|
import CodeViewer from '../CodeViewer';
|
||||||
import NotFound from '../NotFound';
|
import NotFound from '../NotFound';
|
||||||
|
|
||||||
import tutorials from './tutorials.json';
|
import { tutorials } from './tutorials';
|
||||||
|
|
||||||
import withWidth, { isWidthUp } from '@material-ui/core/withWidth';
|
import withWidth, { isWidthUp } from '@material-ui/core/withWidth';
|
||||||
import Tabs from '@material-ui/core/Tabs';
|
import Tabs from '@material-ui/core/Tabs';
|
||||||
@ -24,7 +25,7 @@ class Tutorial extends Component {
|
|||||||
|
|
||||||
componentDidUpdate(props, state){
|
componentDidUpdate(props, state){
|
||||||
if(state.tutorialId !== Number(this.props.match.params.tutorialId)){
|
if(state.tutorialId !== Number(this.props.match.params.tutorialId)){
|
||||||
this.setState({tutorialId: Number(this.props.match.params.tutorialId)})
|
this.setState({ value: 'introduction', tutorialId: Number(this.props.match.params.tutorialId) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +66,8 @@ class Tutorial extends Component {
|
|||||||
'Hier könnte eine Anleitung stehen.': null }
|
'Hier könnte eine Anleitung stehen.': null }
|
||||||
{this.state.value === 'assessment' ?
|
{this.state.value === 'assessment' ?
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12} md={6} lg={8}>
|
<Grid item xs={12} md={6} lg={8} style={{ position: 'relative' }}>
|
||||||
|
<SolutionCheck tutorial={tutorialId-1}/>
|
||||||
<BlocklyWindow />
|
<BlocklyWindow />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} md={6} lg={4}>
|
<Grid item xs={12} md={6} lg={4}>
|
||||||
|
@ -1,15 +1,52 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
import Breadcrumbs from '../Breadcrumbs';
|
import Breadcrumbs from '../Breadcrumbs';
|
||||||
|
|
||||||
import tutorials from './tutorials.json';
|
import { tutorials } from './tutorials';
|
||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||||
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Grid from '@material-ui/core/Grid';
|
import Grid from '@material-ui/core/Grid';
|
||||||
import Paper from '@material-ui/core/Paper';
|
import Paper from '@material-ui/core/Paper';
|
||||||
|
|
||||||
|
import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
const styles = (theme) => ({
|
||||||
|
outerDiv: {
|
||||||
|
position: 'absolute',
|
||||||
|
right: '-29px',
|
||||||
|
bottom: '-29px',
|
||||||
|
width: '140px',
|
||||||
|
height: '140px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: '10px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
borderColor: fade(theme.palette.primary.main, 0.2),
|
||||||
|
color: fade(theme.palette.primary.main, 0.2)
|
||||||
|
},
|
||||||
|
outerDivError: {
|
||||||
|
borderColor: fade(theme.palette.error.dark, 0.2),
|
||||||
|
color: fade(theme.palette.error.dark, 0.2)
|
||||||
|
},
|
||||||
|
innerDiv: {
|
||||||
|
width: 'inherit',
|
||||||
|
height: 'inherit',
|
||||||
|
display: 'table-cell',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
textAlign: 'center'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
class TutorialHome extends Component {
|
class TutorialHome extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -17,17 +54,41 @@ class TutorialHome extends Component {
|
|||||||
|
|
||||||
<h1>Tutorial-Übersicht</h1>
|
<h1>Tutorial-Übersicht</h1>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{tutorials.map((tutorial, i) => (
|
{tutorials.map((tutorial, i) => {
|
||||||
<Grid item xs={12} sm={6} md={4} xl={3} key={i}>
|
var tutorialStatus = this.props.status[i].status === 'success' ? 'Success' :
|
||||||
<Link to={`/tutorial/${i+1}`} style={{textDecoration: 'none', color: 'inherit'}}>
|
this.props.status[i].status === 'error' ? 'Error' : 'Other';
|
||||||
<Paper style={{height: '150px', padding: '10px'}}>{tutorials[i].title}</Paper>
|
return (
|
||||||
</Link>
|
<Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}>
|
||||||
</Grid>
|
<Link to={`/tutorial/${i+1}`} style={{textDecoration: 'none', color: 'inherit'}}>
|
||||||
))}
|
<Paper style={{height: '150px', padding: '10px', position:'relative', overflow: 'hidden'}}>
|
||||||
|
{tutorials[i].title}
|
||||||
|
{tutorialStatus !== 'Other' ?
|
||||||
|
<div className={clsx(this.props.classes.outerDiv, tutorialStatus === 'Error' ? this.props.classes.outerDivError : null)}>
|
||||||
|
<div className={this.props.classes.innerDiv}>
|
||||||
|
<FontAwesomeIcon size='4x' icon={tutorialStatus === 'Success' ? faCheck : faTimes}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</Paper>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
)})}
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TutorialHome;
|
TutorialHome.propTypes = {
|
||||||
|
status: PropTypes.array.isRequired,
|
||||||
|
change: PropTypes.number.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
change: state.tutorial.change,
|
||||||
|
status: state.tutorial.status
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, null)(withStyles(styles, {withTheme: true})(TutorialHome));
|
||||||
|
130
src/components/Tutorial/tutorials.js
Normal file
130
src/components/Tutorial/tutorials.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
export const tutorials = [
|
||||||
|
{
|
||||||
|
"title": "erste Schritte"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "WLAN",
|
||||||
|
"test": function(workspace){
|
||||||
|
var wifi = workspace.getBlocksByType('sensebox_wifi'); // result is an array with Blocks as objects
|
||||||
|
if(wifi.length > 0){
|
||||||
|
var wifiBlock = wifi[wifi.length-1] // first block is probably overwritten
|
||||||
|
if(wifiBlock.getRootBlock().type === 'sensebox_wifi'){
|
||||||
|
return {text: 'Block, um eine WLAN-Verbindung herzustellen, ist nicht verbunden.', type: 'error'}
|
||||||
|
}
|
||||||
|
if(!wifiBlock.getFieldValue('SSID')){
|
||||||
|
return {text: 'Die SSID-Angabe fehlt.', type: 'error'}
|
||||||
|
}
|
||||||
|
if(!wifiBlock.getFieldValue('Password')){
|
||||||
|
return {text: 'Die Angabe des Passworts fehlt.', type: 'error'}
|
||||||
|
}
|
||||||
|
return {text: 'Super. Alles richtig!', type: 'success'}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return {text: 'Der Block, um eine WLAN-Verbindung herzustellen, fehlt.', type: 'error'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "spezifisches WLAN",
|
||||||
|
"test": function(workspace){
|
||||||
|
var wifi = workspace.getBlocksByType('sensebox_wifi'); // result is an array with Blocks as objects
|
||||||
|
if(wifi.length > 0){
|
||||||
|
var wifiBlock = wifi[wifi.length-1] // first block is probably overwritten
|
||||||
|
if(wifiBlock.getRootBlock().type === 'sensebox_wifi'){
|
||||||
|
return {text: 'Block, um eine WLAN-Verbindung herzustellen, ist nicht verbunden.', type: 'error'}
|
||||||
|
}
|
||||||
|
var ssid = wifiBlock.getFieldValue('SSID');
|
||||||
|
if(ssid){
|
||||||
|
if(ssid !== 'SSID'){
|
||||||
|
return {text: 'SSID muss als Angabe "SSID" haben.', type: 'error'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return {text: 'Die SSID-Angabe fehlt.', type: 'error'}
|
||||||
|
}
|
||||||
|
var password = wifiBlock.getFieldValue('Password')
|
||||||
|
if(password){
|
||||||
|
if(password !== 'Passwort'){
|
||||||
|
return {text: 'Password muss als Angabe "Passwort" haben.', type: 'error'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return {text: 'Die Angabe des Passworts fehlt.', type: 'error'}
|
||||||
|
}
|
||||||
|
return {text: 'Super. Alles richtig!', type: 'success'}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return {text: 'Der Block, um eine WLAN-Verbindung herzustellen, fehlt.', type: 'error'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "if-Bedingung"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "for-Schleife"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "erste Schritte"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "if-Bedingung"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "for-Schleife"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "erste Schritte"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "if-Bedingung"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "for-Schleife"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "erste Schritte"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "if-Bedingung"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "for-Schleife"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "erste Schritte"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "if-Bedingung"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "for-Schleife"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "erste Schritte"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "if-Bedingung"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "for-Schleife"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "erste Schritte"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "if-Bedingung"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "for-Schleife"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "erste Schritte"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "if-Bedingung"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "for-Schleife"
|
||||||
|
}
|
||||||
|
]
|
@ -1,74 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"title": "erste Schritte"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "if-Bedingung"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "for-Schleife"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "erste Schritte"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "if-Bedingung"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "for-Schleife"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "erste Schritte"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "if-Bedingung"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "for-Schleife"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "erste Schritte"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "if-Bedingung"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "for-Schleife"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "erste Schritte"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "if-Bedingung"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "for-Schleife"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "erste Schritte"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "if-Bedingung"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "for-Schleife"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "erste Schritte"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "if-Bedingung"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "for-Schleife"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "erste Schritte"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "if-Bedingung"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "for-Schleife"
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,6 +1,8 @@
|
|||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import workspaceReducer from './workspaceReducer';
|
import workspaceReducer from './workspaceReducer';
|
||||||
|
import tutorialReducer from './tutorialReducer';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
workspace: workspaceReducer
|
workspace: workspaceReducer,
|
||||||
|
tutorial: tutorialReducer
|
||||||
});
|
});
|
||||||
|
25
src/reducers/tutorialReducer.js
Normal file
25
src/reducers/tutorialReducer.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE } from '../actions/types';
|
||||||
|
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
status: JSON.parse(window.localStorage.getItem('tutorial')),
|
||||||
|
change: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function(state = initialState, action){
|
||||||
|
switch(action.type){
|
||||||
|
case TUTORIAL_SUCCESS:
|
||||||
|
case TUTORIAL_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
status: action.payload
|
||||||
|
};
|
||||||
|
case TUTORIAL_CHANGE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
change: state.change += 1
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user