Merge branch 'assessment' into tutorial

This commit is contained in:
Delucse 2020-09-07 17:17:14 +02:00
commit e322795f25
12 changed files with 466 additions and 125 deletions

View 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));
};

View File

@ -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';

View File

@ -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">

View 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)));

View File

@ -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)));

View File

@ -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))));

View File

@ -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}>

View File

@ -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));

View 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"
}
]

View File

@ -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"
}
]

View File

@ -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
}); });

View 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;
}
}